Compare commits
No commits in common. "059aebfa28c29bf5225c6dd3af20cda75215fcf5" and "27bcfde6c90b2e586e81557bf06355c4be06b007" have entirely different histories.
059aebfa28
...
27bcfde6c9
@ -26,7 +26,6 @@
|
|||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/signalr": "^10.0.0",
|
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
"@vben/constants": "workspace:*",
|
"@vben/constants": "workspace:*",
|
||||||
|
|||||||
@ -1,171 +0,0 @@
|
|||||||
import { RequestClient } from '@vben/request';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
const imRequestClient = new RequestClient({
|
|
||||||
baseURL: 'http://localhost:5212/api',
|
|
||||||
});
|
|
||||||
|
|
||||||
imRequestClient.addRequestInterceptor({
|
|
||||||
fulfilled: async (config) => {
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
if (accessStore.accessToken) {
|
|
||||||
config.headers.Authorization = `Bearer ${accessStore.accessToken}`;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
imRequestClient.addResponseInterceptor({
|
|
||||||
fulfilled: (response) => {
|
|
||||||
const data = response.data as any;
|
|
||||||
if (data?.code === 0) {
|
|
||||||
response.data = data.data;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export namespace ImApi {
|
|
||||||
export interface Conversation {
|
|
||||||
id: string;
|
|
||||||
type: number;
|
|
||||||
lastMessageContent: null | string;
|
|
||||||
lastMessageAt: null | string;
|
|
||||||
unreadCount: number;
|
|
||||||
groupName: null | string;
|
|
||||||
groupAvatar: null | string;
|
|
||||||
otherUserId: null | string;
|
|
||||||
otherUserName: null | string;
|
|
||||||
otherUserAvatar: null | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
id: string;
|
|
||||||
conversationId: string;
|
|
||||||
senderId: string;
|
|
||||||
senderName: string;
|
|
||||||
senderAvatar: null | string;
|
|
||||||
contentType: number;
|
|
||||||
content: string;
|
|
||||||
metadata: MessageMetadata | null;
|
|
||||||
replyToId: null | string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageMetadata {
|
|
||||||
fileName?: null | string;
|
|
||||||
fileSize?: null | number;
|
|
||||||
fileUrl?: null | string;
|
|
||||||
imageUrl?: null | string;
|
|
||||||
imageWidth?: null | number;
|
|
||||||
imageHeight?: null | number;
|
|
||||||
emojiCode?: null | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageHistory {
|
|
||||||
messages: Message[];
|
|
||||||
hasMore: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UnreadCounts {
|
|
||||||
items: { conversationId: string; unreadCount: number }[];
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupInfo {
|
|
||||||
id: string;
|
|
||||||
conversationId: string;
|
|
||||||
name: string;
|
|
||||||
avatar: null | string;
|
|
||||||
description: null | string;
|
|
||||||
ownerId: string;
|
|
||||||
memberCount: number;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GroupMember {
|
|
||||||
userId: string;
|
|
||||||
userName: string;
|
|
||||||
avatar: null | string;
|
|
||||||
role: number;
|
|
||||||
joinedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
isActive: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
roles: string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getConversationsApi() {
|
|
||||||
return imRequestClient.get<ImApi.Conversation[]>('/conversations');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMessageHistoryApi(
|
|
||||||
conversationId: string,
|
|
||||||
beforeId?: string,
|
|
||||||
limit: number = 50,
|
|
||||||
) {
|
|
||||||
const params: Record<string, any> = { limit };
|
|
||||||
if (beforeId) params.beforeId = beforeId;
|
|
||||||
return imRequestClient.get<ImApi.MessageHistory>(
|
|
||||||
`/conversations/${conversationId}/messages`,
|
|
||||||
{ params },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUnreadCountApi() {
|
|
||||||
return imRequestClient.get<ImApi.UnreadCounts>('/unread');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createGroupApi(data: {
|
|
||||||
avatar?: string;
|
|
||||||
description?: string;
|
|
||||||
memberUserIds: string[];
|
|
||||||
name: string;
|
|
||||||
}) {
|
|
||||||
return imRequestClient.post<ImApi.GroupInfo>('/groups', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateGroupApi(
|
|
||||||
groupId: string,
|
|
||||||
data: { avatar?: string; description?: string; name?: string; },
|
|
||||||
) {
|
|
||||||
return imRequestClient.put<ImApi.GroupInfo>(`/groups/${groupId}`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteGroupApi(groupId: string) {
|
|
||||||
return imRequestClient.delete(`/groups/${groupId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addGroupMemberApi(groupId: string, userIds: string[]) {
|
|
||||||
return imRequestClient.post(`/groups/${groupId}/members`, { userIds });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeGroupMemberApi(groupId: string, userId: string) {
|
|
||||||
return imRequestClient.delete(`/groups/${groupId}/members/${userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getGroupMembersApi(groupId: string) {
|
|
||||||
return imRequestClient.get<ImApi.GroupMember[]>(`/groups/${groupId}/members`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户搜索 — 直连 rag-backend
|
|
||||||
export async function searchUsersApi(search?: string) {
|
|
||||||
const params: Record<string, any> = {};
|
|
||||||
if (search) params.search = search;
|
|
||||||
return requestClient.get<ImApi.User[]>('/users', { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建或获取私聊会话 — IM 后端
|
|
||||||
export async function createPrivateConversationApi(targetUserId: string) {
|
|
||||||
return imRequestClient.post<ImApi.Conversation>('/conversations/private', {
|
|
||||||
targetUserId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,5 +1,3 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './im';
|
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export { signalRService } from './signalr';
|
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
import * as signalR from '@microsoft/signalr';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import type { ImApi } from './im';
|
|
||||||
|
|
||||||
class SignalRService {
|
|
||||||
private connection: signalR.HubConnection | null = null;
|
|
||||||
private reconnectAttempts = 0;
|
|
||||||
private maxReconnectAttempts = 10;
|
|
||||||
|
|
||||||
onMessageReceived: ((message: ImApi.Message) => void) | null = null;
|
|
||||||
onMessageRead:
|
|
||||||
| ((data: { messageId: string; userId: string }) => void)
|
|
||||||
| null = null;
|
|
||||||
onUserTyping:
|
|
||||||
| ((data: { conversationId: string; userId: string }) => void)
|
|
||||||
| null = null;
|
|
||||||
onUserOnline: ((data: { userId: string }) => void) | null = null;
|
|
||||||
onUserOffline: ((data: { userId: string }) => void) | null = null;
|
|
||||||
|
|
||||||
async connect() {
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const token = accessStore.accessToken;
|
|
||||||
|
|
||||||
if (!token) return;
|
|
||||||
|
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
|
||||||
.withUrl('http://localhost:5212/hubs/chat', {
|
|
||||||
accessTokenFactory: () => token,
|
|
||||||
})
|
|
||||||
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
|
|
||||||
.configureLogging(signalR.LogLevel.Warning)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.connection.on('OnMessageReceived', (message: ImApi.Message) => {
|
|
||||||
this.onMessageReceived?.(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.on(
|
|
||||||
'OnMessageRead',
|
|
||||||
(data: { messageId: string; userId: string }) => {
|
|
||||||
this.onMessageRead?.(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.connection.on(
|
|
||||||
'OnUserTyping',
|
|
||||||
(data: { conversationId: string; userId: string }) => {
|
|
||||||
this.onUserTyping?.(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.connection.on('OnUserOnline', (data: { userId: string }) => {
|
|
||||||
this.onUserOnline?.(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.on('OnUserOffline', (data: { userId: string }) => {
|
|
||||||
this.onUserOffline?.(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.onreconnected(() => {
|
|
||||||
this.reconnectAttempts = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.onclose(() => {
|
|
||||||
this.reconnectAttempts++;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.start();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('SignalR connection error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnect() {
|
|
||||||
if (this.connection) {
|
|
||||||
await this.connection.stop();
|
|
||||||
this.connection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage(request: {
|
|
||||||
conversationId: string;
|
|
||||||
contentType: number;
|
|
||||||
content: string;
|
|
||||||
metadata?: any;
|
|
||||||
replyToId?: string;
|
|
||||||
}) {
|
|
||||||
await this.connection?.invoke('SendMessage', request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async markAsRead(conversationId: string, messageId: string) {
|
|
||||||
await this.connection?.invoke('MarkAsRead', { conversationId, messageId });
|
|
||||||
}
|
|
||||||
|
|
||||||
async typing(conversationId: string) {
|
|
||||||
await this.connection?.invoke('Typing', conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isConnected() {
|
|
||||||
return this.connection?.state === signalR.HubConnectionState.Connected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const signalRService = new SignalRService();
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
|
||||||
{
|
|
||||||
meta: {
|
|
||||||
icon: 'lucide:message-square',
|
|
||||||
order: 5,
|
|
||||||
title: '即时通讯',
|
|
||||||
},
|
|
||||||
name: 'Chat',
|
|
||||||
path: '/chat',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'ChatMain',
|
|
||||||
path: '/chat',
|
|
||||||
component: () => import('#/views/im/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: '聊天',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default routes;
|
|
||||||
@ -1,208 +0,0 @@
|
|||||||
import type { ImApi } from '#/api/core/im';
|
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useUserStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import {
|
|
||||||
createPrivateConversationApi,
|
|
||||||
getConversationsApi,
|
|
||||||
getMessageHistoryApi,
|
|
||||||
getUnreadCountApi,
|
|
||||||
searchUsersApi,
|
|
||||||
} from '#/api/core/im';
|
|
||||||
import { signalRService } from '#/api/core/signalr';
|
|
||||||
|
|
||||||
export const useImStore = defineStore('im', () => {
|
|
||||||
const conversations = ref<ImApi.Conversation[]>([]);
|
|
||||||
const activeConversationId = ref<null | string>(null);
|
|
||||||
const messages = ref<Map<string, ImApi.Message[]>>(new Map());
|
|
||||||
const unreadCounts = ref<Map<string, number>>(new Map());
|
|
||||||
const onlineUsers = ref<Set<string>>(new Set());
|
|
||||||
const connected = ref(false);
|
|
||||||
const typingUsers = ref<Map<string, Set<string>>>(new Map());
|
|
||||||
const contacts = ref<ImApi.User[]>([]);
|
|
||||||
const contactsLoading = ref(false);
|
|
||||||
|
|
||||||
const totalUnread = computed(() => {
|
|
||||||
let total = 0;
|
|
||||||
for (const count of unreadCounts.value.values()) {
|
|
||||||
total += count;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeMessages = computed(() => {
|
|
||||||
if (!activeConversationId.value) return [];
|
|
||||||
return messages.value.get(activeConversationId.value) ?? [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeConversation = computed(() => {
|
|
||||||
if (!activeConversationId.value) return null;
|
|
||||||
return conversations.value.find((c) => c.id === activeConversationId.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function connect() {
|
|
||||||
signalRService.onMessageReceived = (message) => {
|
|
||||||
const convMessages = messages.value.get(message.conversationId) ?? [];
|
|
||||||
convMessages.push(message);
|
|
||||||
messages.value.set(message.conversationId, convMessages);
|
|
||||||
|
|
||||||
// Update conversation last message
|
|
||||||
const conv = conversations.value.find(
|
|
||||||
(c) => c.id === message.conversationId,
|
|
||||||
);
|
|
||||||
if (conv) {
|
|
||||||
conv.lastMessageContent =
|
|
||||||
message.contentType === 0
|
|
||||||
? message.content.slice(0, 50)
|
|
||||||
: `[${['文本', '图片', '文件', '系统', '表情'][message.contentType] || '消息'}]`;
|
|
||||||
conv.lastMessageAt = message.createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment unread if not active conversation
|
|
||||||
if (message.conversationId !== activeConversationId.value) {
|
|
||||||
const current = unreadCounts.value.get(message.conversationId) ?? 0;
|
|
||||||
unreadCounts.value.set(message.conversationId, current + 1);
|
|
||||||
const conv2 = conversations.value.find(
|
|
||||||
(c) => c.id === message.conversationId,
|
|
||||||
);
|
|
||||||
if (conv2) conv2.unreadCount++;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
signalRService.onUserOnline = (data) => {
|
|
||||||
onlineUsers.value.add(data.userId);
|
|
||||||
};
|
|
||||||
|
|
||||||
signalRService.onUserOffline = (data) => {
|
|
||||||
onlineUsers.value.delete(data.userId);
|
|
||||||
};
|
|
||||||
|
|
||||||
signalRService.onUserTyping = (data) => {
|
|
||||||
if (!typingUsers.value.has(data.conversationId)) {
|
|
||||||
typingUsers.value.set(data.conversationId, new Set());
|
|
||||||
}
|
|
||||||
typingUsers.value.get(data.conversationId)?.add(data.userId);
|
|
||||||
setTimeout(() => {
|
|
||||||
typingUsers.value.get(data.conversationId)?.delete(data.userId);
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
await signalRService.connect();
|
|
||||||
connected.value = signalRService.isConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function disconnect() {
|
|
||||||
await signalRService.disconnect();
|
|
||||||
connected.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadConversations() {
|
|
||||||
const data = await getConversationsApi();
|
|
||||||
conversations.value = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMessages(conversationId: string, beforeId?: string) {
|
|
||||||
const data = await getMessageHistoryApi(conversationId, beforeId);
|
|
||||||
if (beforeId) {
|
|
||||||
const existing = messages.value.get(conversationId) ?? [];
|
|
||||||
messages.value.set(conversationId, [...data.messages, ...existing]);
|
|
||||||
} else {
|
|
||||||
messages.value.set(conversationId, data.messages);
|
|
||||||
}
|
|
||||||
return data.hasMore;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadUnreadCounts() {
|
|
||||||
const data = await getUnreadCountApi();
|
|
||||||
const map = new Map<string, number>();
|
|
||||||
for (const item of data.items) {
|
|
||||||
map.set(item.conversationId, item.unreadCount);
|
|
||||||
}
|
|
||||||
unreadCounts.value = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendMessage(
|
|
||||||
conversationId: string,
|
|
||||||
contentType: number,
|
|
||||||
content: string,
|
|
||||||
metadata?: any,
|
|
||||||
replyToId?: string,
|
|
||||||
) {
|
|
||||||
await signalRService.sendMessage({
|
|
||||||
conversationId,
|
|
||||||
contentType,
|
|
||||||
content,
|
|
||||||
metadata,
|
|
||||||
replyToId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function markAsRead(conversationId: string, messageId: string) {
|
|
||||||
await signalRService.markAsRead(conversationId, messageId);
|
|
||||||
unreadCounts.value.set(conversationId, 0);
|
|
||||||
const conv = conversations.value.find((c) => c.id === conversationId);
|
|
||||||
if (conv) conv.unreadCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function typing(conversationId: string) {
|
|
||||||
await signalRService.typing(conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveConversation(conversationId: null | string) {
|
|
||||||
activeConversationId.value = conversationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function searchContacts(query?: string) {
|
|
||||||
contactsLoading.value = true;
|
|
||||||
try {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const data = await searchUsersApi(query);
|
|
||||||
contacts.value = data.filter(
|
|
||||||
(u) => u.id !== userStore.userInfo?.userId,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
contactsLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startPrivateChat(targetUserId: string) {
|
|
||||||
const conversation = await createPrivateConversationApi(targetUserId);
|
|
||||||
await loadConversations();
|
|
||||||
setActiveConversation(conversation.id);
|
|
||||||
await loadMessages(conversation.id);
|
|
||||||
return conversation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// State
|
|
||||||
conversations,
|
|
||||||
activeConversationId,
|
|
||||||
messages,
|
|
||||||
unreadCounts,
|
|
||||||
onlineUsers,
|
|
||||||
connected,
|
|
||||||
typingUsers,
|
|
||||||
contacts,
|
|
||||||
contactsLoading,
|
|
||||||
// Computed
|
|
||||||
totalUnread,
|
|
||||||
activeMessages,
|
|
||||||
activeConversation,
|
|
||||||
// Actions
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
loadConversations,
|
|
||||||
loadMessages,
|
|
||||||
loadUnreadCounts,
|
|
||||||
sendMessage,
|
|
||||||
markAsRead,
|
|
||||||
typing,
|
|
||||||
setActiveConversation,
|
|
||||||
searchContacts,
|
|
||||||
startPrivateChat,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@ -1,2 +1 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
export { useImStore } from './im';
|
|
||||||
|
|||||||
@ -1,434 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { ImApi } from '#/api/core/im';
|
|
||||||
|
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useUserStore } from '@vben/stores';
|
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { createGroupApi } from '#/api';
|
|
||||||
import { useImStore } from '#/store';
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const imStore = useImStore();
|
|
||||||
|
|
||||||
const inputMessage = ref('');
|
|
||||||
const sending = ref(false);
|
|
||||||
const showCreateGroup = ref(false);
|
|
||||||
const showGroupManage = ref(false);
|
|
||||||
const newGroupName = ref('');
|
|
||||||
const newGroupDesc = ref('');
|
|
||||||
const selectedMemberIds = ref<string[]>([]);
|
|
||||||
const memberSearchLoading = ref(false);
|
|
||||||
const messageListRef = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
// Sidebar tab
|
|
||||||
const sidebarTab = ref<'contacts' | 'messages'>('messages');
|
|
||||||
const contactSearchQuery = ref('');
|
|
||||||
let contactSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
|
|
||||||
const activeConversationId = computed(() => imStore.activeConversationId);
|
|
||||||
const activeConversation = computed(() => imStore.activeConversation);
|
|
||||||
const activeMessages = computed(() => imStore.activeMessages);
|
|
||||||
|
|
||||||
const sortedConversations = computed(() =>
|
|
||||||
[...imStore.conversations].toSorted((a, b) => {
|
|
||||||
const timeA = a.lastMessageAt ? new Date(a.lastMessageAt).getTime() : 0;
|
|
||||||
const timeB = b.lastMessageAt ? new Date(a.lastMessageAt).getTime() : 0;
|
|
||||||
return timeB - timeA;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
function isSelfMessage(msg: ImApi.Message) {
|
|
||||||
return msg.senderId === userStore.userInfo?.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTime(dateStr: string) {
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
const now = new Date();
|
|
||||||
if (date.toDateString() === now.toDateString()) {
|
|
||||||
return date.toLocaleTimeString('zh-CN', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return date.toLocaleDateString('zh-CN', {
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectConversation(conversationId: string) {
|
|
||||||
imStore.setActiveConversation(conversationId);
|
|
||||||
await imStore.loadMessages(conversationId);
|
|
||||||
await nextTick();
|
|
||||||
scrollToBottom();
|
|
||||||
const msgs = imStore.activeMessages;
|
|
||||||
if (msgs.length > 0) {
|
|
||||||
const lastMsg = msgs[msgs.length - 1];
|
|
||||||
await imStore.markAsRead(conversationId, lastMsg.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSend(e?: any) {
|
|
||||||
if (e?.shiftKey) return;
|
|
||||||
e?.preventDefault?.();
|
|
||||||
|
|
||||||
const content = inputMessage.value.trim();
|
|
||||||
if (!content || !activeConversationId.value) return;
|
|
||||||
|
|
||||||
sending.value = true;
|
|
||||||
try {
|
|
||||||
await imStore.sendMessage(activeConversationId.value, 0, content);
|
|
||||||
inputMessage.value = '';
|
|
||||||
await nextTick();
|
|
||||||
scrollToBottom();
|
|
||||||
} catch {
|
|
||||||
message.error('发送失败');
|
|
||||||
} finally {
|
|
||||||
sending.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleStartChat(user: ImApi.User) {
|
|
||||||
try {
|
|
||||||
await imStore.startPrivateChat(user.id);
|
|
||||||
sidebarTab.value = 'messages';
|
|
||||||
await nextTick();
|
|
||||||
scrollToBottom();
|
|
||||||
} catch {
|
|
||||||
message.error('创建会话失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCreateGroup() {
|
|
||||||
if (!newGroupName.value.trim()) {
|
|
||||||
message.warning('请输入群名称');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await createGroupApi({
|
|
||||||
name: newGroupName.value,
|
|
||||||
description: newGroupDesc.value || undefined,
|
|
||||||
memberUserIds: selectedMemberIds.value,
|
|
||||||
});
|
|
||||||
message.success('群聊创建成功');
|
|
||||||
showCreateGroup.value = false;
|
|
||||||
newGroupName.value = '';
|
|
||||||
newGroupDesc.value = '';
|
|
||||||
selectedMemberIds.value = [];
|
|
||||||
await imStore.loadConversations();
|
|
||||||
} catch {
|
|
||||||
message.error('创建失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleContactSearch() {
|
|
||||||
if (contactSearchTimer) clearTimeout(contactSearchTimer);
|
|
||||||
contactSearchTimer = setTimeout(() => {
|
|
||||||
imStore.searchContacts(contactSearchQuery.value || undefined);
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMemberSearch(value: string) {
|
|
||||||
memberSearchLoading.value = true;
|
|
||||||
imStore.searchContacts(value || undefined).finally(() => {
|
|
||||||
memberSearchLoading.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
if (messageListRef.value) {
|
|
||||||
messageListRef.value.scrollTop = messageListRef.value.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onScroll() {
|
|
||||||
if (!messageListRef.value || !activeConversationId.value) return;
|
|
||||||
if (messageListRef.value.scrollTop === 0) {
|
|
||||||
const msgs = imStore.activeMessages;
|
|
||||||
const firstMsgId = msgs.length > 0 ? msgs[0].id : undefined;
|
|
||||||
if (firstMsgId) {
|
|
||||||
await imStore.loadMessages(activeConversationId.value, firstMsgId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchToContacts() {
|
|
||||||
sidebarTab.value = 'contacts';
|
|
||||||
imStore.searchContacts();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await imStore.connect();
|
|
||||||
await imStore.loadConversations();
|
|
||||||
await imStore.loadUnreadCounts();
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
imStore.disconnect();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="flex h-[calc(100vh-120px)] overflow-hidden rounded-lg border border-border bg-card"
|
|
||||||
>
|
|
||||||
<!-- Sidebar -->
|
|
||||||
<div class="flex w-[300px] shrink-0 flex-col border-r border-border">
|
|
||||||
<!-- Tabs -->
|
|
||||||
<div class="flex border-b border-border">
|
|
||||||
<div
|
|
||||||
class="flex-1 cursor-pointer px-4 py-3 text-center text-sm transition-colors"
|
|
||||||
:class="
|
|
||||||
sidebarTab === 'messages'
|
|
||||||
? 'border-b-2 border-primary font-medium text-foreground'
|
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
|
||||||
"
|
|
||||||
@click="sidebarTab = 'messages'"
|
|
||||||
>
|
|
||||||
消息
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex-1 cursor-pointer px-4 py-3 text-center text-sm transition-colors"
|
|
||||||
:class="
|
|
||||||
sidebarTab === 'contacts'
|
|
||||||
? 'border-b-2 border-primary font-medium text-foreground'
|
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
|
||||||
"
|
|
||||||
@click="switchToContacts"
|
|
||||||
>
|
|
||||||
通讯录
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Messages panel -->
|
|
||||||
<template v-if="sidebarTab === 'messages'">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between border-b border-border px-4 py-2"
|
|
||||||
>
|
|
||||||
<span class="text-sm text-muted-foreground">
|
|
||||||
{{ sortedConversations.length }} 个会话
|
|
||||||
</span>
|
|
||||||
<a-button
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="showCreateGroup = true"
|
|
||||||
>
|
|
||||||
创建群聊
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
v-for="conv in sortedConversations"
|
|
||||||
:key="conv.id"
|
|
||||||
class="flex cursor-pointer items-center px-4 py-3 transition-colors hover:bg-accent"
|
|
||||||
:class="{
|
|
||||||
'bg-primary/10 dark:bg-accent':
|
|
||||||
conv.id === activeConversationId,
|
|
||||||
}"
|
|
||||||
@click="selectConversation(conv.id)"
|
|
||||||
>
|
|
||||||
<a-avatar v-if="conv.type === 2" :size="40">
|
|
||||||
{{ conv.groupName?.charAt(0) || '群' }}
|
|
||||||
</a-avatar>
|
|
||||||
<a-avatar v-else :size="40">
|
|
||||||
{{ conv.otherUserName?.charAt(0) || 'U' }}
|
|
||||||
</a-avatar>
|
|
||||||
|
|
||||||
<div class="ml-3 flex-1 overflow-hidden">
|
|
||||||
<div class="truncate text-sm font-medium text-foreground">
|
|
||||||
{{
|
|
||||||
conv.type === 2
|
|
||||||
? conv.groupName
|
|
||||||
: conv.otherUserName || '私聊'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 truncate text-xs text-muted-foreground">
|
|
||||||
{{ conv.lastMessageContent || '暂无消息' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a-badge
|
|
||||||
v-if="conv.unreadCount > 0"
|
|
||||||
:count="conv.unreadCount"
|
|
||||||
:overflow-count="99"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Contacts panel -->
|
|
||||||
<template v-else>
|
|
||||||
<div class="border-b border-border px-3 py-2">
|
|
||||||
<a-input-search
|
|
||||||
v-model:value="contactSearchQuery"
|
|
||||||
placeholder="搜索用户名或邮箱"
|
|
||||||
@search="imStore.searchContacts(contactSearchQuery || undefined)"
|
|
||||||
@change="handleContactSearch"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
|
||||||
<a-spin :spinning="imStore.contactsLoading">
|
|
||||||
<div
|
|
||||||
v-for="user in imStore.contacts"
|
|
||||||
:key="user.id"
|
|
||||||
class="flex cursor-pointer items-center px-4 py-3 transition-colors hover:bg-accent"
|
|
||||||
@click="handleStartChat(user)"
|
|
||||||
>
|
|
||||||
<a-avatar :size="40">
|
|
||||||
{{ user.username.charAt(0).toUpperCase() }}
|
|
||||||
</a-avatar>
|
|
||||||
<div class="ml-3 flex-1 overflow-hidden">
|
|
||||||
<div class="truncate text-sm font-medium text-foreground">
|
|
||||||
{{ user.username }}
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 truncate text-xs text-muted-foreground">
|
|
||||||
{{ user.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
v-if="imStore.onlineUsers.has(user.id)"
|
|
||||||
class="mr-2 inline-block h-2 w-2 rounded-full bg-green-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
!imStore.contactsLoading && imStore.contacts.length === 0
|
|
||||||
"
|
|
||||||
class="px-4 py-8 text-center text-sm text-muted-foreground"
|
|
||||||
>
|
|
||||||
没有找到用户
|
|
||||||
</div>
|
|
||||||
</a-spin>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main chat area -->
|
|
||||||
<div class="flex flex-1 flex-col">
|
|
||||||
<template v-if="activeConversation">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between border-b border-border px-4 py-3 font-medium text-foreground"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
activeConversation.type === 2
|
|
||||||
? activeConversation.groupName
|
|
||||||
: activeConversation.otherUserName || '私聊'
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<a-button
|
|
||||||
v-if="activeConversation.type === 2"
|
|
||||||
size="small"
|
|
||||||
@click="showGroupManage = true"
|
|
||||||
>
|
|
||||||
群管理
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref="messageListRef"
|
|
||||||
class="flex-1 overflow-y-auto px-4 py-4"
|
|
||||||
@scroll="onScroll"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="msg in activeMessages"
|
|
||||||
:key="msg.id"
|
|
||||||
class="mb-4"
|
|
||||||
:class="{ 'text-right': isSelfMessage(msg) }"
|
|
||||||
>
|
|
||||||
<div class="mb-1 text-xs text-muted-foreground">
|
|
||||||
{{ msg.senderName || msg.senderId.substring(0, 8) }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="inline-block max-w-[60%] break-words rounded-lg px-3 py-2"
|
|
||||||
:class="
|
|
||||||
isSelfMessage(msg)
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-muted text-foreground'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template v-if="msg.contentType === 0">
|
|
||||||
{{ msg.content }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="msg.contentType === 1">
|
|
||||||
<img
|
|
||||||
:src="msg.metadata?.imageUrl"
|
|
||||||
class="max-w-[200px] rounded-lg"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="msg.contentType === 4">
|
|
||||||
<span class="text-2xl">{{ msg.content }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
[{{
|
|
||||||
['文本', '图片', '文件', '系统', '表情'][msg.contentType]
|
|
||||||
}}]
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 text-[11px] text-muted-foreground/60">
|
|
||||||
{{ formatTime(msg.createdAt) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2 border-t border-border px-4 py-3">
|
|
||||||
<a-textarea
|
|
||||||
v-model:value="inputMessage"
|
|
||||||
placeholder="输入消息..."
|
|
||||||
:auto-size="{ minRows: 1, maxRows: 4 }"
|
|
||||||
@press-enter="handleSend"
|
|
||||||
/>
|
|
||||||
<a-button type="primary" @click="handleSend" :loading="sending">
|
|
||||||
发送
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex flex-1 items-center justify-center text-muted-foreground"
|
|
||||||
>
|
|
||||||
<p>选择一个会话开始聊天</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Create Group Modal -->
|
|
||||||
<a-modal
|
|
||||||
v-model:open="showCreateGroup"
|
|
||||||
title="创建群聊"
|
|
||||||
@ok="handleCreateGroup"
|
|
||||||
>
|
|
||||||
<a-form layout="vertical">
|
|
||||||
<a-form-item label="群名称">
|
|
||||||
<a-input v-model:value="newGroupName" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="群描述">
|
|
||||||
<a-input v-model:value="newGroupDesc" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="选择成员">
|
|
||||||
<a-select
|
|
||||||
v-model:value="selectedMemberIds"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="搜索并选择成员"
|
|
||||||
:filter-option="false"
|
|
||||||
:loading="memberSearchLoading"
|
|
||||||
@search="handleMemberSearch"
|
|
||||||
>
|
|
||||||
<a-select-option
|
|
||||||
v-for="user in imStore.contacts"
|
|
||||||
:key="user.id"
|
|
||||||
:value="user.id"
|
|
||||||
>
|
|
||||||
{{ user.username }} ({{ user.email }})
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
93
pnpm-lock.yaml
generated
93
pnpm-lock.yaml
generated
@ -591,9 +591,6 @@ importers:
|
|||||||
|
|
||||||
apps/web-antd:
|
apps/web-antd:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/signalr':
|
|
||||||
specifier: ^10.0.0
|
|
||||||
version: 10.0.0
|
|
||||||
'@vben/access':
|
'@vben/access':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/effects/access
|
version: link:../../packages/effects/access
|
||||||
@ -3330,9 +3327,6 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@microsoft/signalr@10.0.0':
|
|
||||||
resolution: {integrity: sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==}
|
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.4':
|
'@napi-rs/wasm-runtime@1.1.4':
|
||||||
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
|
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -6227,10 +6221,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
engines: {node: '>=0.8.x'}
|
engines: {node: '>=0.8.x'}
|
||||||
|
|
||||||
eventsource@2.0.2:
|
|
||||||
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
|
|
||||||
engines: {node: '>=12.0.0'}
|
|
||||||
|
|
||||||
execa@9.6.1:
|
execa@9.6.1:
|
||||||
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
||||||
engines: {node: ^18.19.0 || >=20.5.0}
|
engines: {node: ^18.19.0 || >=20.5.0}
|
||||||
@ -6301,9 +6291,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||||
engines: {node: ^12.20 || >= 14.13}
|
engines: {node: ^12.20 || >= 14.13}
|
||||||
|
|
||||||
fetch-cookie@2.2.0:
|
|
||||||
resolution: {integrity: sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==}
|
|
||||||
|
|
||||||
fflate@0.8.2:
|
fflate@0.8.2:
|
||||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||||
|
|
||||||
@ -8073,9 +8060,6 @@ packages:
|
|||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
|
||||||
psl@1.15.0:
|
|
||||||
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
|
|
||||||
|
|
||||||
publint@0.3.18:
|
publint@0.3.18:
|
||||||
resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==}
|
resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -8108,9 +8092,6 @@ packages:
|
|||||||
quansync@1.0.0:
|
quansync@1.0.0:
|
||||||
resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
|
resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
|
||||||
|
|
||||||
querystringify@2.2.0:
|
|
||||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@ -8230,9 +8211,6 @@ packages:
|
|||||||
require-package-name@2.0.1:
|
require-package-name@2.0.1:
|
||||||
resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==}
|
resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==}
|
||||||
|
|
||||||
requires-port@1.0.0:
|
|
||||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
|
||||||
|
|
||||||
resize-observer-polyfill@1.5.1:
|
resize-observer-polyfill@1.5.1:
|
||||||
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||||
|
|
||||||
@ -8537,9 +8515,6 @@ packages:
|
|||||||
set-blocking@2.0.0:
|
set-blocking@2.0.0:
|
||||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2:
|
|
||||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -8995,10 +8970,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
|
||||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
tr46@0.0.3:
|
tr46@0.0.3:
|
||||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
@ -9179,10 +9150,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
universalify@0.2.0:
|
|
||||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
|
||||||
engines: {node: '>= 4.0.0'}
|
|
||||||
|
|
||||||
universalify@2.0.1:
|
universalify@2.0.1:
|
||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@ -9338,9 +9305,6 @@ packages:
|
|||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
url-parse@1.5.10:
|
|
||||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
|
||||||
|
|
||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@ -9732,18 +9696,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==}
|
resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==}
|
||||||
engines: {node: ^20.17.0 || >=22.9.0}
|
engines: {node: ^20.17.0 || >=22.9.0}
|
||||||
|
|
||||||
ws@7.5.10:
|
|
||||||
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
|
|
||||||
engines: {node: '>=8.3.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: ^5.0.2
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
ws@8.20.0:
|
ws@8.20.0:
|
||||||
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@ -11738,18 +11690,6 @@ snapshots:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@microsoft/signalr@10.0.0':
|
|
||||||
dependencies:
|
|
||||||
abort-controller: 3.0.0
|
|
||||||
eventsource: 2.0.2
|
|
||||||
fetch-cookie: 2.2.0
|
|
||||||
node-fetch: 2.7.0
|
|
||||||
ws: 7.5.10
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- bufferutil
|
|
||||||
- encoding
|
|
||||||
- utf-8-validate
|
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.10.0
|
'@emnapi/core': 1.10.0
|
||||||
@ -14673,8 +14613,6 @@ snapshots:
|
|||||||
|
|
||||||
events@3.3.0: {}
|
events@3.3.0: {}
|
||||||
|
|
||||||
eventsource@2.0.2: {}
|
|
||||||
|
|
||||||
execa@9.6.1:
|
execa@9.6.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sindresorhus/merge-streams': 4.0.0
|
'@sindresorhus/merge-streams': 4.0.0
|
||||||
@ -14745,11 +14683,6 @@ snapshots:
|
|||||||
node-domexception: 1.0.0
|
node-domexception: 1.0.0
|
||||||
web-streams-polyfill: 3.3.3
|
web-streams-polyfill: 3.3.3
|
||||||
|
|
||||||
fetch-cookie@2.2.0:
|
|
||||||
dependencies:
|
|
||||||
set-cookie-parser: 2.7.2
|
|
||||||
tough-cookie: 4.1.4
|
|
||||||
|
|
||||||
fflate@0.8.2: {}
|
fflate@0.8.2: {}
|
||||||
|
|
||||||
figures@6.1.0:
|
figures@6.1.0:
|
||||||
@ -16576,10 +16509,6 @@ snapshots:
|
|||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
psl@1.15.0:
|
|
||||||
dependencies:
|
|
||||||
punycode: 2.3.1
|
|
||||||
|
|
||||||
publint@0.3.18:
|
publint@0.3.18:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@publint/pack': 0.1.4
|
'@publint/pack': 0.1.4
|
||||||
@ -16611,8 +16540,6 @@ snapshots:
|
|||||||
|
|
||||||
quansync@1.0.0: {}
|
quansync@1.0.0: {}
|
||||||
|
|
||||||
querystringify@2.2.0: {}
|
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
radix3@1.1.2: {}
|
radix3@1.1.2: {}
|
||||||
@ -16760,8 +16687,6 @@ snapshots:
|
|||||||
|
|
||||||
require-package-name@2.0.1: {}
|
require-package-name@2.0.1: {}
|
||||||
|
|
||||||
requires-port@1.0.0: {}
|
|
||||||
|
|
||||||
resize-observer-polyfill@1.5.1: {}
|
resize-observer-polyfill@1.5.1: {}
|
||||||
|
|
||||||
resolve-dir@1.0.1:
|
resolve-dir@1.0.1:
|
||||||
@ -17080,8 +17005,6 @@ snapshots:
|
|||||||
|
|
||||||
set-blocking@2.0.0: {}
|
set-blocking@2.0.0: {}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2: {}
|
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
define-data-property: 1.1.4
|
define-data-property: 1.1.4
|
||||||
@ -17594,13 +17517,6 @@ snapshots:
|
|||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
tough-cookie@4.1.4:
|
|
||||||
dependencies:
|
|
||||||
psl: 1.15.0
|
|
||||||
punycode: 2.3.1
|
|
||||||
universalify: 0.2.0
|
|
||||||
url-parse: 1.5.10
|
|
||||||
|
|
||||||
tr46@0.0.3: {}
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
tr46@1.0.1:
|
tr46@1.0.1:
|
||||||
@ -17797,8 +17713,6 @@ snapshots:
|
|||||||
|
|
||||||
universalify@0.1.2: {}
|
universalify@0.1.2: {}
|
||||||
|
|
||||||
universalify@0.2.0: {}
|
|
||||||
|
|
||||||
universalify@2.0.1: {}
|
universalify@2.0.1: {}
|
||||||
|
|
||||||
unplugin-dts@1.0.0-beta.6(esbuild@0.27.7)(rolldown@1.0.0-rc.17)(rollup@4.60.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.3)):
|
unplugin-dts@1.0.0-beta.6(esbuild@0.27.7)(rolldown@1.0.0-rc.17)(rollup@4.60.2)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.3)):
|
||||||
@ -17931,11 +17845,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
url-parse@1.5.10:
|
|
||||||
dependencies:
|
|
||||||
querystringify: 2.2.0
|
|
||||||
requires-port: 1.0.0
|
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
valibot@1.3.1(typescript@6.0.3):
|
valibot@1.3.1(typescript@6.0.3):
|
||||||
@ -18414,8 +18323,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
ws@7.5.10: {}
|
|
||||||
|
|
||||||
ws@8.20.0: {}
|
ws@8.20.0: {}
|
||||||
|
|
||||||
wsl-utils@0.1.0:
|
wsl-utils@0.1.0:
|
||||||
|
|||||||
@ -1,156 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"name": "@vben/web-antd",
|
|
||||||
"path": "apps/web-antd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/commitlint-config",
|
|
||||||
"path": "internal/lint-configs/commitlint-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/eslint-config",
|
|
||||||
"path": "internal/lint-configs/eslint-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/oxfmt-config",
|
|
||||||
"path": "internal/lint-configs/oxfmt-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/oxlint-config",
|
|
||||||
"path": "internal/lint-configs/oxlint-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/stylelint-config",
|
|
||||||
"path": "internal/lint-configs/stylelint-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/node-utils",
|
|
||||||
"path": "internal/node-utils"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/tailwind-config",
|
|
||||||
"path": "internal/tailwind-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/tsconfig",
|
|
||||||
"path": "internal/tsconfig"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/vite-config",
|
|
||||||
"path": "internal/vite-config"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/design",
|
|
||||||
"path": "packages/@core/base/design"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/icons",
|
|
||||||
"path": "packages/@core/base/icons"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/shared",
|
|
||||||
"path": "packages/@core/base/shared"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/typings",
|
|
||||||
"path": "packages/@core/base/typings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/composables",
|
|
||||||
"path": "packages/@core/composables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/preferences",
|
|
||||||
"path": "packages/@core/preferences"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/form-ui",
|
|
||||||
"path": "packages/@core/ui-kit/form-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/layout-ui",
|
|
||||||
"path": "packages/@core/ui-kit/layout-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/menu-ui",
|
|
||||||
"path": "packages/@core/ui-kit/menu-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/popup-ui",
|
|
||||||
"path": "packages/@core/ui-kit/popup-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/shadcn-ui",
|
|
||||||
"path": "packages/@core/ui-kit/shadcn-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben-core/tabs-ui",
|
|
||||||
"path": "packages/@core/ui-kit/tabs-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/constants",
|
|
||||||
"path": "packages/constants"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/access",
|
|
||||||
"path": "packages/effects/access"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/common-ui",
|
|
||||||
"path": "packages/effects/common-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/hooks",
|
|
||||||
"path": "packages/effects/hooks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/layouts",
|
|
||||||
"path": "packages/effects/layouts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/plugins",
|
|
||||||
"path": "packages/effects/plugins"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/request",
|
|
||||||
"path": "packages/effects/request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/icons",
|
|
||||||
"path": "packages/icons"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/locales",
|
|
||||||
"path": "packages/locales"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/preferences",
|
|
||||||
"path": "packages/preferences"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/stores",
|
|
||||||
"path": "packages/stores"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/styles",
|
|
||||||
"path": "packages/styles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/types",
|
|
||||||
"path": "packages/types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/utils",
|
|
||||||
"path": "packages/utils"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/turbo-run",
|
|
||||||
"path": "scripts/turbo-run"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/vsh",
|
|
||||||
"path": "scripts/vsh"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user