vue-chat-kit 0.3.9 → 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 +4420 -2877
  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 +503 -2818
  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
@@ -0,0 +1,98 @@
1
+ import { createApp, ref, render, h, nextTick } from 'vue'
2
+ import MessageBox from '../components/ui/MessageBox.vue'
3
+
4
+ const createMessageBox = (options) => {
5
+ const {
6
+ title = '提示',
7
+ message = '',
8
+ showCancelButton = true,
9
+ showConfirmButton = true,
10
+ cancelButtonText = '取消',
11
+ confirmButtonText = '确定',
12
+ closeOnClickModal = true,
13
+ width = '420px'
14
+ } = options
15
+
16
+ const container = document.createElement('div')
17
+ document.body.appendChild(container)
18
+
19
+ const vnode = h(MessageBox, {
20
+ title,
21
+ message,
22
+ showCancelButton,
23
+ showConfirmButton,
24
+ cancelButtonText,
25
+ confirmButtonText,
26
+ closeOnClickModal,
27
+ width,
28
+ modelValue: false,
29
+ onConfirm: () => {},
30
+ onCancel: () => {},
31
+ onClose: () => {}
32
+ })
33
+
34
+ render(vnode, container)
35
+
36
+ const instance = vnode.component
37
+
38
+ const close = () => {
39
+ if (instance.exposed) {
40
+ instance.exposed.close()
41
+ }
42
+ setTimeout(() => {
43
+ render(null, container)
44
+ document.body.removeChild(container)
45
+ }, 300)
46
+ }
47
+
48
+ return new Promise((resolve, reject) => {
49
+ instance.props.onConfirm = () => {
50
+ resolve('confirm')
51
+ close()
52
+ }
53
+
54
+ instance.props.onCancel = () => {
55
+ reject('cancel')
56
+ close()
57
+ }
58
+
59
+ instance.props.onClose = () => {
60
+ reject('close')
61
+ close()
62
+ }
63
+
64
+ nextTick(() => {
65
+ if (instance.exposed) {
66
+ instance.exposed.open().catch((e) => {
67
+ if (e !== 'cancel' && e !== 'close') {
68
+ console.error(e)
69
+ }
70
+ })
71
+ }
72
+ })
73
+ })
74
+ }
75
+
76
+ export const useMessageBox = () => ({
77
+ alert: (message, title = '提示') => createMessageBox({
78
+ message,
79
+ title,
80
+ showCancelButton: false
81
+ }),
82
+ confirm: (message, title = '提示', options = {}) => createMessageBox({
83
+ message,
84
+ title,
85
+ ...options
86
+ }),
87
+ prompt: (message, title = '提示', options = {}) => createMessageBox({
88
+ message,
89
+ title,
90
+ ...options
91
+ })
92
+ })
93
+
94
+ export const ElMessage = {
95
+ alert: (message, title) => useMessageBox().alert(message, title),
96
+ confirm: (message, title, options) => useMessageBox().confirm(message, title, options),
97
+ prompt: (message, title, options) => useMessageBox().prompt(message, title, options)
98
+ }
@@ -0,0 +1,140 @@
1
+ // ==========================================
2
+ // 主题管理 Composable
3
+ // ==========================================
4
+
5
+ import { ref, onMounted, onUnmounted, computed } from 'vue'
6
+ import { THEMES, DEFAULT_THEME, THEME_STORAGE_KEY } from '../const/theme.js'
7
+
8
+ /**
9
+ * 获取系统主题
10
+ * @returns {string} 'light' | 'dark'
11
+ */
12
+ function getSystemTheme() {
13
+ if (typeof window !== 'undefined' && window.matchMedia) {
14
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
15
+ ? THEMES.DARK
16
+ : THEMES.LIGHT
17
+ }
18
+ return DEFAULT_THEME
19
+ }
20
+
21
+ /**
22
+ * 应用主题到 DOM
23
+ * @param {string} theme 主题名称
24
+ */
25
+ function applyThemeToDOM(theme) {
26
+ if (typeof document !== 'undefined') {
27
+ const root = document.documentElement
28
+ root.setAttribute('data-theme', theme)
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 保存主题到本地存储
34
+ * @param {string} theme 主题名称
35
+ */
36
+ function saveThemeToStorage(theme) {
37
+ if (typeof localStorage !== 'undefined') {
38
+ localStorage.setItem(THEME_STORAGE_KEY, theme)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 从本地存储获取主题
44
+ * @returns {string | null}
45
+ */
46
+ function getThemeFromStorage() {
47
+ if (typeof localStorage !== 'undefined') {
48
+ return localStorage.getItem(THEME_STORAGE_KEY)
49
+ }
50
+ return null
51
+ }
52
+
53
+ export function useTheme() {
54
+ // 当前用户选择的主题
55
+ const currentTheme = ref(DEFAULT_THEME)
56
+
57
+ // 实际应用的主题(考虑 auto 模式)
58
+ const appliedTheme = ref(DEFAULT_THEME)
59
+
60
+ // 是否是浅色主题
61
+ const isLight = computed(() => appliedTheme.value === THEMES.LIGHT)
62
+
63
+ // 是否是深色主题
64
+ const isDark = computed(() => appliedTheme.value === THEMES.DARK)
65
+
66
+ // 系统主题变化监听器
67
+ let mediaQueryList = null
68
+
69
+ /**
70
+ * 设置主题
71
+ * @param {string} theme 主题名称
72
+ */
73
+ function setTheme(theme) {
74
+ // 验证主题有效性
75
+ const validTheme = Object.values(THEMES).includes(theme) ? theme : DEFAULT_THEME
76
+ currentTheme.value = validTheme
77
+
78
+ // 计算实际应用的主题
79
+ if (validTheme === THEMES.AUTO) {
80
+ appliedTheme.value = getSystemTheme()
81
+ } else {
82
+ appliedTheme.value = validTheme
83
+ }
84
+
85
+ // 应用到 DOM
86
+ applyThemeToDOM(appliedTheme.value)
87
+
88
+ // 保存到存储
89
+ saveThemeToStorage(validTheme)
90
+ }
91
+
92
+ /**
93
+ * 系统主题变化处理
94
+ */
95
+ function handleSystemThemeChange(e) {
96
+ if (currentTheme.value === THEMES.AUTO) {
97
+ appliedTheme.value = e.matches ? THEMES.DARK : THEMES.LIGHT
98
+ applyThemeToDOM(appliedTheme.value)
99
+ }
100
+ }
101
+
102
+ /**
103
+ * 初始化主题
104
+ */
105
+ function initTheme() {
106
+ // 从存储获取主题
107
+ const savedTheme = getThemeFromStorage()
108
+ if (savedTheme) {
109
+ setTheme(savedTheme)
110
+ } else {
111
+ setTheme(DEFAULT_THEME)
112
+ }
113
+
114
+ // 监听系统主题变化
115
+ if (typeof window !== 'undefined' && window.matchMedia) {
116
+ mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
117
+ mediaQueryList.addEventListener('change', handleSystemThemeChange)
118
+ }
119
+ }
120
+
121
+ // 生命周期
122
+ onMounted(() => {
123
+ initTheme()
124
+ })
125
+
126
+ onUnmounted(() => {
127
+ if (mediaQueryList) {
128
+ mediaQueryList.removeEventListener('change', handleSystemThemeChange)
129
+ }
130
+ })
131
+
132
+ return {
133
+ currentTheme,
134
+ appliedTheme,
135
+ isLight,
136
+ isDark,
137
+ setTheme,
138
+ THEMES
139
+ }
140
+ }
@@ -26,6 +26,7 @@ const defaultConfig = {
26
26
  addGroupMember: '/chart/group/member/add',
27
27
  getGroupMembers: '/chart/group/member/list',
28
28
  quitGroup: '/chart/group/member/quit',
29
+ getGroupChatStatus: '/chart/group/status',
29
30
  getGroupHistory: '/chart/group/history',
30
31
  getGroupUnreadCount: '/chart/group/unread/count',
31
32
  getMsgReadUserList: '/chart/group/msg/read/list',
@@ -0,0 +1 @@
1
+ export { themeColorMap, elPlusVars, DEFAULT_THEME } from './theme.js';
@@ -0,0 +1,19 @@
1
+ // ==========================================
2
+ // 主题常量配置
3
+ // ==========================================
4
+
5
+ export const THEMES = {
6
+ LIGHT: 'light',
7
+ DARK: 'dark',
8
+ AUTO: 'auto'
9
+ }
10
+
11
+ export const DEFAULT_THEME = THEMES.LIGHT
12
+
13
+ export const THEME_OPTIONS = [
14
+ { value: THEMES.LIGHT, label: '浅色', icon: 'sun' },
15
+ { value: THEMES.DARK, label: '深色', icon: 'moon' },
16
+ { value: THEMES.AUTO, label: '跟随系统', icon: 'monitor' }
17
+ ]
18
+
19
+ export const THEME_STORAGE_KEY = 'vck-theme'
package/src/core/api.js CHANGED
@@ -73,8 +73,8 @@ export class ChatApi {
73
73
  return this._call('addFriend', currentUser, friendUser)
74
74
  }
75
75
 
76
- async _addFriend(currentUser, friendUser) {
77
- return this.http.post(this.endpoints.addFriend, { currentUser, friendUser })
76
+ async _addFriend(applyUser, friendUser) {
77
+ return this.http.post(this.endpoints.addFriend, { applyUser, friendUser })
78
78
  }
79
79
 
80
80
  /**
@@ -373,6 +373,17 @@ export class ChatApi {
373
373
  async _readAllGroupMsg(groupId, currentUser) {
374
374
  return this.http.post(this.endpoints.readAllGroupMsg, { groupId, currentUser })
375
375
  }
376
+
377
+ /**
378
+ * 设置群聊聊天状态
379
+ */
380
+ async setGroupChatStatus(groupId, currentUser, status = 1) {
381
+ return this._call('setGroupChatStatus', groupId, currentUser, status)
382
+ }
383
+
384
+ async _setGroupChatStatus(groupId, currentUser, status) {
385
+ return this.http.post(this.endpoints.getGroupChatStatus, null, { params: { groupId, currentUser, status } })
386
+ }
376
387
  }
377
388
 
378
389
  export default ChatApi
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // 导入样式
2
- import './style.css'
2
+ import './styles/index.scss'
3
3
 
4
4
  // 先导入组件用于 Vue 插件
5
5
  import ChatPanel from './components/ChatPanel.vue'
@@ -8,11 +8,14 @@ import AvatarCrop from './components/AvatarCrop.vue'
8
8
  // 导出组件 - 新的独立面板组件(推荐使用)
9
9
  export { ChatPanel }
10
10
 
11
-
12
11
  export { AvatarCrop }
13
12
 
14
13
  // 导出 composables
15
14
  export { useChat } from './composables/useChat.js'
15
+ export { useTheme } from './composables/useTheme.js'
16
+
17
+ // 导出主题常量
18
+ export { THEMES, DEFAULT_THEME, THEME_OPTIONS, THEME_STORAGE_KEY } from './const/theme.js'
16
19
 
17
20
  // 导出配置
18
21
  export { createChatConfig } from './config/index.js'
@@ -22,9 +25,6 @@ export { ChatWebSocket } from './core/websocket.js'
22
25
  export { ChatApi } from './core/api.js'
23
26
  export { HttpClient } from './core/request.js'
24
27
 
25
- // 导出类型(如果使用 TypeScript 的话)
26
- // export * from './types/index.d.ts'
27
-
28
28
  // 安装函数,用于 Vue 插件安装
29
29
  export const VueChatKit = {
30
30
  install(app) {
@@ -0,0 +1,38 @@
1
+ // ==========================================
2
+ // 基础样式
3
+ // ==========================================
4
+
5
+ // 引入主题系统
6
+ @use './themes/index';
7
+
8
+ // 重置样式
9
+ *,
10
+ *::before,
11
+ *::after {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ // 滚动条样式
16
+ ::-webkit-scrollbar {
17
+ width: 6px;
18
+ height: 6px;
19
+ }
20
+
21
+ ::-webkit-scrollbar-thumb {
22
+ background: var(--vck-scrollbar);
23
+ border-radius: 3px;
24
+ transition: var(--vck-transition-fast);
25
+ }
26
+
27
+ ::-webkit-scrollbar-thumb:hover {
28
+ background: var(--vck-scrollbar-hover);
29
+ }
30
+
31
+ ::-webkit-scrollbar-track {
32
+ background: transparent;
33
+ }
34
+
35
+ // 通用的过渡效果
36
+ .vck-transition {
37
+ transition: var(--vck-transition);
38
+ }
@@ -0,0 +1,43 @@
1
+ // ==================== 颜色变量 ====================
2
+ $vck-primary: #07c160;
3
+ $vck-primary-hover: #06ad56;
4
+ $vck-success: #10b981;
5
+ $vck-danger: #ef4444;
6
+ $vck-warning: #f59e0b;
7
+
8
+ // 背景色
9
+ $vck-bg-color: #ffffff;
10
+ $vck-bg-panel: #f9fafb;
11
+ $vck-bg-sidebar: #f5f5f5;
12
+ $vck-bg-chat: #fafafa;
13
+ $vck-bg-bubble-self: #95ec69;
14
+ $vck-bg-bubble-other: #ffffff;
15
+
16
+ // 文字颜色
17
+ $vck-text-primary: #1f2937;
18
+ $vck-text-secondary: #6b7280;
19
+ $vck-text-muted: #9ca3af;
20
+ $vck-text-white: #ffffff;
21
+
22
+ // 边框颜色
23
+ $vck-border: #e5e7eb;
24
+ $vck-border-light: #f3f4f6;
25
+ $vck-avatar-border: #e5e7eb;
26
+
27
+ // 滚动条
28
+ $vck-scrollbar: #ccc;
29
+
30
+ // 阴影
31
+ $vck-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 12px rgba(0, 0, 0, 0.08);
32
+ $vck-shadow-light: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
33
+
34
+ // ==================== 尺寸变量 ====================
35
+ $vck-border-radius-sm: 6px;
36
+ $vck-border-radius: 8px;
37
+ $vck-border-radius-md: 12px;
38
+ $vck-border-radius-lg: 16px;
39
+ $vck-border-radius-xl: 20px;
40
+ $vck-border-radius-full: 9999px;
41
+
42
+ $vck-transition: all 0.3s ease;
43
+ $vck-transition-fast: all 0.2s ease;
@@ -0,0 +1,45 @@
1
+ // ==========================================
2
+ // AddFriendDialog 组件样式
3
+ // ==========================================
4
+
5
+ .add-friend-search-wrapper {
6
+ margin-bottom: 16px;
7
+ }
8
+
9
+ .add-friend-users-list {
10
+ max-height: 400px;
11
+ overflow-y: auto;
12
+ }
13
+
14
+ .add-friend-user-item {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ padding: 12px;
19
+ margin-bottom: 8px;
20
+ border-radius: var(--vck-radius);
21
+ transition: var(--vck-transition-fast);
22
+ }
23
+
24
+ .add-friend-user-item:hover {
25
+ background-color: var(--vck-bg-panel);
26
+ }
27
+
28
+ .add-friend-user-info {
29
+ display: flex;
30
+ align-items: center;
31
+ }
32
+
33
+ .add-friend-user-avatar {
34
+ width: 40px;
35
+ height: 40px;
36
+ border-radius: 50%;
37
+ object-fit: cover;
38
+ }
39
+
40
+ .add-friend-user-name {
41
+ margin-left: 12px;
42
+ font-weight: 500;
43
+ color: var(--vck-text-primary);
44
+ font-size: 14px;
45
+ }
@@ -0,0 +1,120 @@
1
+ // ==========================================
2
+ // AvatarCrop 组件样式
3
+ // ==========================================
4
+
5
+ .avatar-crop-container {
6
+ min-height: 200px;
7
+ }
8
+
9
+ .crop-wrapper {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 16px;
13
+ }
14
+
15
+ .crop-area {
16
+ position: relative;
17
+ overflow: hidden;
18
+ background: var(--vck-bg-panel);
19
+ border-radius: var(--vck-radius-lg);
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ border: 1px solid var(--vck-border);
24
+ }
25
+
26
+ .crop-box {
27
+ position: absolute;
28
+ border: 2px solid var(--vck-primary);
29
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
30
+ cursor: move;
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ .crop-handle {
35
+ position: absolute;
36
+ width: 14px;
37
+ height: 14px;
38
+ background: var(--vck-primary);
39
+ border: 3px solid var(--vck-text-white);
40
+ border-radius: 50%;
41
+ box-shadow: var(--vck-shadow-sm);
42
+ }
43
+
44
+ .crop-handle.nw {
45
+ top: -7px;
46
+ left: -7px;
47
+ cursor: nw-resize;
48
+ }
49
+ .crop-handle.n {
50
+ top: -7px;
51
+ left: 50%;
52
+ transform: translateX(-50%);
53
+ cursor: n-resize;
54
+ }
55
+ .crop-handle.ne {
56
+ top: -7px;
57
+ right: -7px;
58
+ cursor: ne-resize;
59
+ }
60
+ .crop-handle.e {
61
+ right: -7px;
62
+ top: 50%;
63
+ transform: translateY(-50%);
64
+ cursor: e-resize;
65
+ }
66
+ .crop-handle.se {
67
+ bottom: -7px;
68
+ right: -7px;
69
+ cursor: se-resize;
70
+ }
71
+ .crop-handle.s {
72
+ bottom: -7px;
73
+ left: 50%;
74
+ transform: translateX(-50%);
75
+ cursor: s-resize;
76
+ }
77
+ .crop-handle.sw {
78
+ bottom: -7px;
79
+ left: -7px;
80
+ cursor: sw-resize;
81
+ }
82
+ .crop-handle.w {
83
+ left: -7px;
84
+ top: 50%;
85
+ transform: translateY(-50%);
86
+ cursor: w-resize;
87
+ }
88
+
89
+ .preview-wrapper {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ gap: 16px;
94
+ }
95
+
96
+ .preview-label {
97
+ font-size: 14px;
98
+ color: var(--vck-text-secondary);
99
+ font-weight: 500;
100
+ }
101
+
102
+ .preview-box {
103
+ border-radius: 50%;
104
+ overflow: hidden;
105
+ border: 3px solid var(--vck-border);
106
+ background: var(--vck-bg);
107
+ box-shadow: var(--vck-shadow-sm);
108
+ }
109
+
110
+ .preview-canvas {
111
+ display: block;
112
+ width: 100%;
113
+ height: 100%;
114
+ }
115
+
116
+ .dialog-footer {
117
+ display: flex;
118
+ justify-content: center;
119
+ gap: 12px;
120
+ }