diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 7b31553..672e3df 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -26,6 +26,7 @@ "#/*": "./src/*" }, "dependencies": { + "@microsoft/signalr": "^10.0.0", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", "@vben/constants": "workspace:*", diff --git a/apps/web-antd/src/api/core/im.ts b/apps/web-antd/src/api/core/im.ts new file mode 100644 index 0000000..04cfe5d --- /dev/null +++ b/apps/web-antd/src/api/core/im.ts @@ -0,0 +1,146 @@ +import { RequestClient } from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +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: string | null; + lastMessageAt: string | null; + unreadCount: number; + groupName: string | null; + groupAvatar: string | null; + otherUserId: string | null; + otherUserName: string | null; + otherUserAvatar: string | null; + } + + export interface Message { + id: string; + conversationId: string; + senderId: string; + senderName: string; + senderAvatar: string | null; + contentType: number; + content: string; + metadata: MessageMetadata | null; + replyToId: string | null; + createdAt: string; + } + + export interface MessageMetadata { + fileName?: string | null; + fileSize?: number | null; + fileUrl?: string | null; + imageUrl?: string | null; + imageWidth?: number | null; + imageHeight?: number | null; + emojiCode?: string | null; + } + + 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: string | null; + description: string | null; + ownerId: string; + memberCount: number; + createdAt: string; + } + + export interface GroupMember { + userId: string; + userName: string; + avatar: string | null; + role: number; + joinedAt: string; + } +} + +export async function getConversationsApi() { + return imRequestClient.get('/conversations'); +} + +export async function getMessageHistoryApi( + conversationId: string, + beforeId?: string, + limit: number = 50, +) { + const params: Record = { limit }; + if (beforeId) params.beforeId = beforeId; + return imRequestClient.get( + `/conversations/${conversationId}/messages`, + { params }, + ); +} + +export async function getUnreadCountApi() { + return imRequestClient.get('/unread'); +} + +export async function createGroupApi(data: { + name: string; + avatar?: string; + description?: string; + memberUserIds: string[]; +}) { + return imRequestClient.post('/groups', data); +} + +export async function updateGroupApi( + groupId: string, + data: { name?: string; avatar?: string; description?: string }, +) { + return imRequestClient.put(`/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(`/groups/${groupId}/members`); +} diff --git a/apps/web-antd/src/api/core/index.ts b/apps/web-antd/src/api/core/index.ts index 28a5aef..02598d9 100644 --- a/apps/web-antd/src/api/core/index.ts +++ b/apps/web-antd/src/api/core/index.ts @@ -1,3 +1,5 @@ export * from './auth'; +export * from './im'; export * from './menu'; export * from './user'; +export { signalRService } from './signalr'; diff --git a/apps/web-antd/src/api/core/signalr.ts b/apps/web-antd/src/api/core/signalr.ts new file mode 100644 index 0000000..765c62e --- /dev/null +++ b/apps/web-antd/src/api/core/signalr.ts @@ -0,0 +1,106 @@ +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(); diff --git a/apps/web-antd/src/router/routes/modules/im.ts b/apps/web-antd/src/router/routes/modules/im.ts new file mode 100644 index 0000000..e2dacdc --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/im.ts @@ -0,0 +1,25 @@ +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; diff --git a/apps/web-antd/src/store/im.ts b/apps/web-antd/src/store/im.ts new file mode 100644 index 0000000..8c4fc59 --- /dev/null +++ b/apps/web-antd/src/store/im.ts @@ -0,0 +1,176 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +import type { ImApi } from '#/api/core/im'; + +import { + getConversationsApi, + getMessageHistoryApi, + getUnreadCountApi, +} from '#/api/core/im'; +import { signalRService } from '#/api/core/signalr'; + +export const useImStore = defineStore('im', () => { + const conversations = ref([]); + const activeConversationId = ref(null); + const messages = ref>(new Map()); + const unreadCounts = ref>(new Map()); + const onlineUsers = ref>(new Set()); + const connected = ref(false); + const typingUsers = ref>>(new Map()); + + 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.substring(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(); + 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: string | null) { + activeConversationId.value = conversationId; + } + + return { + // State + conversations, + activeConversationId, + messages, + unreadCounts, + onlineUsers, + connected, + typingUsers, + // Computed + totalUnread, + activeMessages, + activeConversation, + // Actions + connect, + disconnect, + loadConversations, + loadMessages, + loadUnreadCounts, + sendMessage, + markAsRead, + typing, + setActiveConversation, + }; +}); diff --git a/apps/web-antd/src/store/index.ts b/apps/web-antd/src/store/index.ts index 269586e..7ac3140 100644 --- a/apps/web-antd/src/store/index.ts +++ b/apps/web-antd/src/store/index.ts @@ -1 +1,2 @@ export * from './auth'; +export { useImStore } from './im'; diff --git a/apps/web-antd/src/views/im/index.vue b/apps/web-antd/src/views/im/index.vue new file mode 100644 index 0000000..55483a7 --- /dev/null +++ b/apps/web-antd/src/views/im/index.vue @@ -0,0 +1,408 @@ + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5029bfc..5d3f110 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -591,6 +591,9 @@ importers: apps/web-antd: dependencies: + '@microsoft/signalr': + specifier: ^10.0.0 + version: 10.0.0 '@vben/access': specifier: workspace:* version: link:../../packages/effects/access @@ -3327,6 +3330,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@microsoft/signalr@10.0.0': + resolution: {integrity: sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -6221,6 +6227,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 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: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} @@ -6291,6 +6301,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fetch-cookie@2.2.0: + resolution: {integrity: sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -8060,6 +8073,9 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + publint@0.3.18: resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==} engines: {node: '>=18'} @@ -8092,6 +8108,9 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -8211,6 +8230,9 @@ packages: require-package-name@2.0.1: resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -8515,6 +8537,9 @@ packages: set-blocking@2.0.0: 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: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -8970,6 +8995,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -9150,6 +9179,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 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: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -9305,6 +9338,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -9696,6 +9732,18 @@ packages: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} 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: resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} @@ -11690,6 +11738,18 @@ snapshots: - encoding - 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)': dependencies: '@emnapi/core': 1.10.0 @@ -14613,6 +14673,8 @@ snapshots: events@3.3.0: {} + eventsource@2.0.2: {} + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -14683,6 +14745,11 @@ snapshots: node-domexception: 1.0.0 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: {} figures@6.1.0: @@ -16509,6 +16576,10 @@ snapshots: prr@1.0.1: optional: true + psl@1.15.0: + dependencies: + punycode: 2.3.1 + publint@0.3.18: dependencies: '@publint/pack': 0.1.4 @@ -16540,6 +16611,8 @@ snapshots: quansync@1.0.0: {} + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} radix3@1.1.2: {} @@ -16687,6 +16760,8 @@ snapshots: require-package-name@2.0.1: {} + requires-port@1.0.0: {} + resize-observer-polyfill@1.5.1: {} resolve-dir@1.0.1: @@ -17005,6 +17080,8 @@ snapshots: set-blocking@2.0.0: {} + set-cookie-parser@2.7.2: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -17517,6 +17594,13 @@ snapshots: 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@1.0.1: @@ -17713,6 +17797,8 @@ snapshots: universalify@0.1.2: {} + universalify@0.2.0: {} + 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)): @@ -17845,6 +17931,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + util-deprecate@1.0.2: {} valibot@1.3.1(typescript@6.0.3): @@ -18323,6 +18414,8 @@ snapshots: dependencies: signal-exit: 4.1.0 + ws@7.5.10: {} + ws@8.20.0: {} wsl-utils@0.1.0: diff --git a/vben-admin.code-workspace b/vben-admin.code-workspace new file mode 100644 index 0000000..1794287 --- /dev/null +++ b/vben-admin.code-workspace @@ -0,0 +1,156 @@ +{ + "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" + } + ] +}