vue-chat-kit 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/vue-chat-kit.css +1 -1
  2. package/dist/vue-chat-kit.es.js +4426 -2819
  3. package/dist/vue-chat-kit.umd.js +1 -1
  4. package/package.json +1 -1
  5. package/src/components/AvatarCrop.vue +16 -127
  6. package/src/components/ChatPanel.vue +491 -2675
  7. package/src/components/EmojiPicker.vue +2 -73
  8. package/src/components/chat/ChatWindow.vue +177 -0
  9. package/src/components/chat/ContentList.vue +300 -0
  10. package/src/components/chat/ContextMenu.vue +32 -0
  11. package/src/components/chat/GroupSidebar.vue +284 -0
  12. package/src/components/chat/MainArea.vue +87 -0
  13. package/src/components/chat/Sidebar.vue +52 -0
  14. package/src/components/chat/dialogs/AddFriendDialog.vue +62 -0
  15. package/src/components/chat/dialogs/CreateGroupDialog.vue +86 -0
  16. package/src/components/chat/dialogs/GroupDetailDialog.vue +132 -0
  17. package/src/components/ui/Button.vue +190 -0
  18. package/src/components/ui/Dialog.vue +194 -0
  19. package/src/components/ui/Empty.vue +66 -0
  20. package/src/components/ui/Input.vue +166 -0
  21. package/src/components/ui/Message.vue +186 -0
  22. package/src/components/ui/MessageBox.vue +143 -0
  23. package/src/components/ui/MessageManager.vue +92 -0
  24. package/src/components/ui/Switch.vue +65 -0
  25. package/src/components/ui/Tag.vue +68 -0
  26. package/src/components/ui/icons/ArrowDown.vue +5 -0
  27. package/src/components/ui/icons/ArrowRight.vue +5 -0
  28. package/src/components/ui/icons/Bell.vue +6 -0
  29. package/src/components/ui/icons/Camera.vue +6 -0
  30. package/src/components/ui/icons/ChatDotRound.vue +5 -0
  31. package/src/components/ui/icons/Check.vue +5 -0
  32. package/src/components/ui/icons/CircleCheck.vue +6 -0
  33. package/src/components/ui/icons/Clock.vue +6 -0
  34. package/src/components/ui/icons/Close.vue +5 -0
  35. package/src/components/ui/icons/Delete.vue +8 -0
  36. package/src/components/ui/icons/Edit.vue +6 -0
  37. package/src/components/ui/icons/Folder.vue +5 -0
  38. package/src/components/ui/icons/Minus.vue +5 -0
  39. package/src/components/ui/icons/Monitor.vue +7 -0
  40. package/src/components/ui/icons/Moon.vue +5 -0
  41. package/src/components/ui/icons/Picture.vue +7 -0
  42. package/src/components/ui/icons/Plus.vue +5 -0
  43. package/src/components/ui/icons/Search.vue +6 -0
  44. package/src/components/ui/icons/Setting.vue +6 -0
  45. package/src/components/ui/icons/Sunny.vue +6 -0
  46. package/src/components/ui/icons/User.vue +6 -0
  47. package/src/components/ui/icons/UserFilled.vue +6 -0
  48. package/src/components/ui/icons/Warning.vue +5 -0
  49. package/src/components/ui/icons/index.js +24 -0
  50. package/src/components/ui/index.js +10 -0
  51. package/src/composables/useFriendChat.js +10 -14
  52. package/src/composables/useGroupChat.js +140 -48
  53. package/src/composables/useMessage.js +21 -0
  54. package/src/composables/useMessageBox.js +98 -0
  55. package/src/composables/useTheme.js +140 -0
  56. package/src/config/index.js +1 -0
  57. package/src/const/index.js +1 -0
  58. package/src/const/theme.js +19 -0
  59. package/src/core/api.js +13 -2
  60. package/src/index.js +5 -5
  61. package/src/styles/_base.scss +38 -0
  62. package/src/styles/_variables.scss +43 -0
  63. package/src/styles/components/_add-friend-dialog.scss +45 -0
  64. package/src/styles/components/_avatar-crop.scss +120 -0
  65. package/src/styles/components/_chat-panel.scss +546 -0
  66. package/src/styles/components/_chat-window.scss +239 -0
  67. package/src/styles/components/_content-list.scss +260 -0
  68. package/src/styles/components/_context-menu.scss +35 -0
  69. package/src/styles/components/_create-group-dialog.scss +78 -0
  70. package/src/styles/components/_dialogs.scss +226 -0
  71. package/src/styles/components/_emoji-picker.scss +74 -0
  72. package/src/styles/components/_group-detail-dialog.scss +110 -0
  73. package/src/styles/components/_group-sidebar.scss +278 -0
  74. package/src/styles/components/_main-area.scss +94 -0
  75. package/src/styles/components/_sidebar.scss +83 -0
  76. package/src/styles/index.scss +18 -0
  77. package/src/styles/themes/_dark.scss +68 -0
  78. package/src/styles/themes/_index.scss +7 -0
  79. package/src/styles/themes/_light.scss +69 -0
@@ -121,77 +121,6 @@ const selectEmoji = (emoji) => {
121
121
  }
122
122
  </script>
123
123
 
124
- <style scoped>
125
- .emoji-picker {
126
- position: absolute;
127
- bottom: 100%;
128
- left: 0;
129
- width: 320px;
130
- background: rgba(255, 255, 255, 0.9);
131
- backdrop-filter: blur(20px);
132
- -webkit-backdrop-filter: blur(20px);
133
- border-radius: 16px;
134
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
135
- border: 1px solid rgba(255, 255, 255, 0.3);
136
- overflow: hidden;
137
- z-index: 1000;
138
- margin-bottom: 8px;
139
- }
140
-
141
- .emoji-header {
142
- display: flex;
143
- padding: 8px 12px;
144
- border-bottom: 1px solid rgba(0, 0, 0, 0.08);
145
- gap: 4px;
146
- }
147
-
148
- .emoji-category-tab {
149
- padding: 8px 12px;
150
- cursor: pointer;
151
- border-radius: 8px;
152
- transition: all 0.2s;
153
- font-size: 18px;
154
- line-height: 1;
155
- }
156
-
157
- .emoji-category-tab:hover {
158
- background: rgba(0, 0, 0, 0.05);
159
- }
160
-
161
- .emoji-category-tab.active {
162
- background: rgba(7, 193, 96, 0.1);
163
- }
164
-
165
- .emoji-body {
166
- display: grid;
167
- grid-template-columns: repeat(8, 1fr);
168
- gap: 4px;
169
- padding: 12px;
170
- max-height: 240px;
171
- overflow-y: auto;
172
- }
173
-
174
- .emoji-body::-webkit-scrollbar {
175
- width: 6px;
176
- }
177
-
178
- .emoji-body::-webkit-scrollbar-thumb {
179
- background: rgba(0, 0, 0, 0.2);
180
- border-radius: 3px;
181
- }
182
-
183
- .emoji-item {
184
- font-size: 24px;
185
- padding: 8px;
186
- text-align: center;
187
- cursor: pointer;
188
- border-radius: 8px;
189
- transition: all 0.15s;
190
- line-height: 1;
191
- }
192
-
193
- .emoji-item:hover {
194
- background: rgba(0, 0, 0, 0.08);
195
- transform: scale(1.2);
196
- }
124
+ <style scoped lang="scss">
125
+ @use '../styles/components/emoji-picker';
197
126
  </style>
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <div class="chat-window-area">
3
+ <!-- 顶部标题栏 -->
4
+ <div class="chat-window-header">
5
+ <div class="chat-window-title">
6
+ <span class="chat-window-name">{{ currentSelectGroup ? currentSelectGroup.name : currentChat.name }}</span>
7
+ <span
8
+ v-if="!currentSelectGroup"
9
+ :class="[
10
+ 'chat-window-status',
11
+ currentChat.online ? 'chat-window-status-online' : 'chat-window-status-offline'
12
+ ]"
13
+ >
14
+ {{ currentChat.online ? '在线' : '离线' }}
15
+ </span>
16
+ <span v-if="currentSelectGroup" class="chat-window-status">
17
+ {{ groupMemberList.length }} 人
18
+ </span>
19
+ </div>
20
+ <div class="chat-window-actions">
21
+ <el-icon class="chat-action-icon"><Search /></el-icon>
22
+ <el-icon
23
+ v-if="currentSelectGroup"
24
+ class="chat-action-icon"
25
+ @click="$emit('toggle-group-sidebar')"
26
+ ><MoreFilled /></el-icon>
27
+ <el-icon
28
+ v-else
29
+ class="chat-action-icon"
30
+ @click="$emit('toggle-chat-detail')"
31
+ ><MoreFilled /></el-icon>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- 聊天消息区域 -->
36
+ <div
37
+ ref="messagesContainer"
38
+ class="chat-messages-container"
39
+ >
40
+ <div
41
+ v-for="(msg, index) in messages"
42
+ :key="index"
43
+ :class="[
44
+ 'message-wrapper',
45
+ msg.isSelf ? 'message-self' : 'message-other'
46
+ ]"
47
+ >
48
+ <!-- 头像 -->
49
+ <div class="message-avatar">
50
+ <img
51
+ :src="msg.avatar"
52
+ class="message-avatar-img"
53
+ />
54
+ </div>
55
+
56
+ <!-- 消息内容 -->
57
+ <div
58
+ :class="[
59
+ 'message-content',
60
+ msg.isSelf ? 'message-content-self' : 'message-content-other'
61
+ ]"
62
+ >
63
+ <div v-if="!msg.isSelf" class="message-sender-name">
64
+ {{ currentSelectGroup ? (msg.displayName || msg.sendUsername) : currentChat.name }}
65
+ </div>
66
+
67
+ <div class="message-bubble-wrapper">
68
+ <!-- 文本消息 -->
69
+ <div
70
+ v-if="msg.type === 'text'"
71
+ :class="[
72
+ 'message-bubble',
73
+ msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
74
+ ]"
75
+ >
76
+ {{ msg.text }}
77
+ </div>
78
+
79
+ <!-- 图片文件消息 -->
80
+ <div
81
+ v-else-if="msg.type === 'file' && msg.fileType === 'image'"
82
+ :class="[
83
+ 'message-bubble',
84
+ 'message-image-bubble',
85
+ msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
86
+ ]"
87
+ @click="$emit('open-file', msg.fileUrl)"
88
+ >
89
+ <img
90
+ :src="msg.fileUrl"
91
+ :alt="msg.fileName"
92
+ class="message-image"
93
+ />
94
+ <div v-if="msg.fileSize" class="message-image-size">{{ formatFileSize(msg.fileSize) }}</div>
95
+ </div>
96
+
97
+ <!-- 文档文件消息 -->
98
+ <div
99
+ v-else-if="msg.type === 'file'"
100
+ :class="[
101
+ 'message-bubble',
102
+ 'message-file-bubble',
103
+ msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
104
+ ]"
105
+ @click="$emit('open-file', msg.fileUrl)"
106
+ >
107
+ <div class="message-file-content">
108
+ <div class="message-file-icon">
109
+ <el-icon :size="28"><Document /></el-icon>
110
+ </div>
111
+ <div class="message-file-info">
112
+ <div class="message-file-name">{{ msg.fileName || msg.text }}</div>
113
+ <div class="message-file-meta">
114
+ <el-icon :size="12"><Download /></el-icon>
115
+ <span>点击下载</span>
116
+ <span v-if="msg.fileSize">· {{ formatFileSize(msg.fileSize) }}</span>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+
122
+ <!-- 时间显示在气泡下方 -->
123
+ <div
124
+ :class="[
125
+ 'message-time',
126
+ msg.isSelf ? 'message-time-right' : 'message-time-left'
127
+ ]"
128
+ >
129
+ {{ formatTime(msg.time) }}
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- 底部输入区域 -->
137
+ <div class="chat-input-area" @click="$emit('hide-emoji')">
138
+ <slot name="input-area"></slot>
139
+ </div>
140
+ </div>
141
+ </template>
142
+
143
+ <script setup>
144
+ import { ref, onMounted, watch } from 'vue'
145
+ import { Search, MoreFilled, Document, Download } from '@element-plus/icons-vue'
146
+
147
+ const props = defineProps({
148
+ currentChat: Object,
149
+ currentSelectGroup: Object,
150
+ groupMemberList: Array,
151
+ messages: Array,
152
+ formatTime: Function,
153
+ formatFileSize: Function
154
+ })
155
+
156
+ const emit = defineEmits(['toggle-group-sidebar', 'toggle-chat-detail', 'open-file', 'hide-emoji'])
157
+
158
+ const messagesContainer = ref(null)
159
+
160
+ const scrollToBottom = () => {
161
+ if (messagesContainer.value) {
162
+ messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
163
+ }
164
+ }
165
+
166
+ watch(() => props.messages, () => {
167
+ setTimeout(scrollToBottom, 50)
168
+ }, { deep: true })
169
+
170
+ onMounted(scrollToBottom)
171
+
172
+ defineExpose({ scrollToBottom })
173
+ </script>
174
+
175
+ <style scoped lang="scss">
176
+ @use '../../styles/components/chat-window';
177
+ </style>
@@ -0,0 +1,300 @@
1
+ <template>
2
+ <div class="chat-content-panel">
3
+ <!-- 搜索栏 -->
4
+ <div class="chat-search-bar">
5
+ <Input
6
+ :model-value="searchText"
7
+ @update:model-value="$emit('update:searchText', $event)"
8
+ placeholder="搜索"
9
+ class="chat-search-input"
10
+ >
11
+ <template #prefix>
12
+ <Search />
13
+ </template>
14
+ </Input>
15
+ <div class="search-actions">
16
+ <Button
17
+ v-if="config.modules.friends"
18
+ type="primary"
19
+ size="small"
20
+ @click="$emit('add-friend')"
21
+ title="添加好友"
22
+ >
23
+ <template #icon>
24
+ <User />
25
+ </template>
26
+ </Button>
27
+ <Button
28
+ v-if="config.modules.groups"
29
+ type="primary"
30
+ size="small"
31
+ @click="$emit('create-group')"
32
+ title="创建群聊"
33
+ >
34
+ <template #icon>
35
+ <UserFilled />
36
+ </template>
37
+ </Button>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- 内容区域 -->
42
+ <div class="chat-content-scroll">
43
+ <!-- 聊天列表(好友+群聊合并) -->
44
+ <div v-if="currentNavTab === 'chat'">
45
+ <div
46
+ v-for="chat in combinedChatList"
47
+ :key="chat.type + '_' + (chat.id || chat.groupId)"
48
+ :class="[
49
+ 'chat-list-item',
50
+ isChatActive(chat) ? 'chat-list-item-active' : ''
51
+ ]"
52
+ @click="handleChatClick(chat)"
53
+ @contextmenu.prevent.stop="$emit('contextmenu', $event, chat)"
54
+ >
55
+ <div class="chat-list-avatar-wrapper">
56
+ <!-- 群聊头像:多方格 -->
57
+ <div v-if="chat.type === 'group' && chat.memberAvatars && chat.memberAvatars.length > 0" class="group-avatar-grid">
58
+ <div
59
+ v-for="(member, index) in chat.memberAvatars.slice(0, 4)"
60
+ :key="member.username || index"
61
+ class="group-avatar-item"
62
+ >
63
+ <img
64
+ :src="member.avatar"
65
+ :alt="member.username"
66
+ class="group-avatar-img"
67
+ @error="handleAvatarError"
68
+ />
69
+ </div>
70
+ </div>
71
+ <!-- 单头像 -->
72
+ <img
73
+ v-else
74
+ :src="chat.avatar"
75
+ :alt="chat.name"
76
+ class="chat-list-avatar"
77
+ />
78
+ <span
79
+ v-if="chat.type === 'friend' && chat.online"
80
+ class="chat-list-online-indicator"
81
+ ></span>
82
+ </div>
83
+ <div class="chat-list-info">
84
+ <div class="chat-list-header">
85
+ <span class="chat-list-name">{{ chat.name }}</span>
86
+ <span class="chat-list-time">{{ formatLastTime(chat.lastTime) }}</span>
87
+ </div>
88
+ <div class="chat-list-preview">
89
+ <span class="chat-list-last-msg">
90
+ {{ chat.type === 'group' && chat.lastMsgSender ? chat.lastMsgSender + ': ' : '' }}{{ chat.lastMsg }}
91
+ </span>
92
+ <span
93
+ v-if="chat.unread > 0"
94
+ class="chat-list-unread"
95
+ >
96
+ {{ chat.unread > 99 ? '99+' : chat.unread }}
97
+ </span>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- 好友和群聊列表(合并) -->
104
+ <div v-if="currentNavTab === 'contacts'">
105
+ <!-- 好友分组 -->
106
+ <div v-if="config.modules.friends">
107
+ <div class="list-section-title" @click="friendsCollapsed = !friendsCollapsed">
108
+ <span :class="['collapse-icon', friendsCollapsed ? 'collapsed' : '']">
109
+ <ArrowDown />
110
+ </span>
111
+ <span>好友</span>
112
+ </div>
113
+ <div v-show="!friendsCollapsed" class="list-content">
114
+ <div
115
+ v-for="friend in filteredFriendList"
116
+ :key="friend.id"
117
+ class="chat-list-item"
118
+ @click="$emit('select-friend', friend)"
119
+ >
120
+ <div class="chat-list-avatar-wrapper">
121
+ <img
122
+ :src="friend.avatar"
123
+ :alt="friend.name"
124
+ class="chat-list-avatar"
125
+ />
126
+ <span
127
+ :class="[
128
+ 'chat-list-online-indicator',
129
+ friend.online ? 'chat-list-online' : 'chat-list-offline'
130
+ ]"
131
+ ></span>
132
+ </div>
133
+ <div class="chat-list-info">
134
+ <span class="chat-list-name">{{ friend.name }}</span>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <!-- 群聊分组 -->
141
+ <div v-if="config.modules.groups">
142
+ <div class="list-section-title" @click="groupsCollapsed = !groupsCollapsed">
143
+ <span :class="['collapse-icon', groupsCollapsed ? 'collapsed' : '']">
144
+ <ArrowDown />
145
+ </span>
146
+ <span>群聊</span>
147
+ </div>
148
+ <div v-show="!groupsCollapsed" class="list-content">
149
+ <div
150
+ v-for="group in filteredGroupList"
151
+ :key="group.groupId"
152
+ :class="[
153
+ 'chat-list-item',
154
+ currentSelectedGroup?.groupId === group.groupId ? 'chat-list-item-active' : ''
155
+ ]"
156
+ @click="$emit('select-group', group)"
157
+ >
158
+ <div class="chat-list-avatar-wrapper">
159
+ <!-- 多方格群聊头像 -->
160
+ <div v-if="group.memberAvatars && group.memberAvatars.length > 0" class="group-avatar-grid">
161
+ <div
162
+ v-for="(member, index) in group.memberAvatars.slice(0, 4)"
163
+ :key="member.username || index"
164
+ class="group-avatar-item"
165
+ >
166
+ <img
167
+ :src="member.avatar"
168
+ :alt="member.username"
169
+ class="group-avatar-img"
170
+ @error="handleAvatarError"
171
+ />
172
+ </div>
173
+ </div>
174
+ <!-- 单个头像作为后备 -->
175
+ <img
176
+ v-else
177
+ :src="group.avatar"
178
+ :alt="group.name"
179
+ class="chat-list-avatar"
180
+ />
181
+ </div>
182
+ <div class="chat-list-info">
183
+ <div class="chat-list-header">
184
+ <span class="chat-list-name">{{ group.name }}</span>
185
+ <span class="chat-list-time">{{ formatLastTime(group.lastTime) }}</span>
186
+ </div>
187
+ <div class="chat-list-preview">
188
+ <span class="chat-list-last-msg">
189
+ {{ group.lastMsgSender ? group.lastMsgSender + ': ' : '' }}{{ group.lastMsg || '暂无消息' }}
190
+ </span>
191
+ <span
192
+ v-if="group.unread > 0"
193
+ class="chat-list-unread"
194
+ >
195
+ {{ group.unread > 99 ? '99+' : group.unread }}
196
+ </span>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- 申请列表 -->
205
+ <div v-if="currentNavTab === 'apply' && config.modules.apply">
206
+ <Empty v-if="loadingFriendApply" description="加载中..." />
207
+ <Empty
208
+ v-else-if="friendApplyList.length === 0"
209
+ description="暂无好友申请"
210
+ />
211
+ <div
212
+ v-else
213
+ v-for="apply in friendApplyList"
214
+ :key="apply.applyUser || apply.id"
215
+ class="friend-request-item"
216
+ >
217
+ <div class="friend-request-info">
218
+ <img
219
+ :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${apply.applyUser}`"
220
+ :alt="apply.applyUser"
221
+ class="friend-request-avatar"
222
+ />
223
+ <div class="friend-request-details">
224
+ <div class="friend-request-username">{{ apply.applyUser }}</div>
225
+ <div class="friend-request-desc">请求添加你为好友</div>
226
+ </div>
227
+ </div>
228
+ <Button
229
+ type="primary"
230
+ size="small"
231
+ @click="$emit('agree-friend', apply.applyUser)"
232
+ >同意</Button>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </template>
238
+
239
+ <script setup>
240
+ import { ref } from 'vue'
241
+ import { Search, User, UserFilled, ArrowDown } from '../ui/icons/index.js'
242
+ import { Button, Input, Empty } from '../ui/index.js'
243
+
244
+ const props = defineProps({
245
+ searchText: String,
246
+ currentNavTab: String,
247
+ config: Object,
248
+ combinedChatList: Array,
249
+ filteredFriendList: Array,
250
+ filteredGroupList: Array,
251
+ currentChatId: [String, Number],
252
+ currentSelectGroup: Object,
253
+ currentSelectedGroup: Object,
254
+ loadingFriendApply: Boolean,
255
+ friendApplyList: Array,
256
+ formatLastTime: Function
257
+ })
258
+
259
+ const emit = defineEmits([
260
+ 'update:searchText',
261
+ 'add-friend',
262
+ 'create-group',
263
+ 'select-chat',
264
+ 'select-group-chat',
265
+ 'select-friend',
266
+ 'select-group',
267
+ 'agree-friend',
268
+ 'contextmenu'
269
+ ])
270
+
271
+ const friendsCollapsed = ref(false)
272
+ const groupsCollapsed = ref(false)
273
+
274
+ const isChatActive = (chat) => {
275
+ if (chat.type === 'friend') {
276
+ return props.currentChatId === chat.id
277
+ }
278
+ return props.currentSelectGroup?.groupId === chat.groupId
279
+ }
280
+
281
+ const handleChatClick = (chat) => {
282
+ if (chat.type === 'friend') {
283
+ emit('select-chat', chat)
284
+ } else {
285
+ emit('select-group-chat', chat)
286
+ }
287
+ }
288
+
289
+ // 处理头像加载错误
290
+ const handleAvatarError = (e) => {
291
+ const img = e.target
292
+ // 头像加载失败时,显示默认背景色
293
+ img.style.display = 'none'
294
+ img.parentElement.style.backgroundColor = '#e0e0e0'
295
+ }
296
+ </script>
297
+
298
+ <style scoped lang="scss">
299
+ @use '../../styles/components/content-list';
300
+ </style>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <div
4
+ v-if="visible"
5
+ class="context-menu"
6
+ :style="{ left: x + 'px', top: y + 'px' }"
7
+ >
8
+ <slot></slot>
9
+ </div>
10
+ </Teleport>
11
+ </template>
12
+
13
+ <script setup>
14
+ defineProps({
15
+ visible: {
16
+ type: Boolean,
17
+ default: false
18
+ },
19
+ x: {
20
+ type: Number,
21
+ default: 0
22
+ },
23
+ y: {
24
+ type: Number,
25
+ default: 0
26
+ }
27
+ })
28
+ </script>
29
+
30
+ <style scoped lang="scss">
31
+ @use '../../styles/components/context-menu';
32
+ </style>