im-ui-mobile 0.0.3 → 0.0.5

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.
package/package.json CHANGED
@@ -1,16 +1,12 @@
1
1
  {
2
2
  "name": "im-ui-mobile",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "A Uniapp instant messaging component library based on Vue3.0+typescript",
5
5
  "main": "index.ts",
6
6
  "module": "dist/im-ui-mobile.es.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
- "index.js",
10
- "index.d.ts",
11
- "components/",
12
- "types/",
13
- "utils/"
9
+ "src/"
14
10
  ],
15
11
  "scripts": {
16
12
  "build": "vite build",
@@ -0,0 +1,167 @@
1
+ <!-- packages/chat-box/index.vue -->
2
+ <template>
3
+ <div class="u-im-chat-box">
4
+ <div class="u-im-chat-box__header">
5
+ <div class="u-im-chat-box__title">{{ title }}</div>
6
+ <div class="u-im-chat-box__actions">
7
+ <slot name="header-actions"></slot>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="u-im-chat-box__messages" ref="messagesRef">
12
+ <div class="u-im-chat-box__loading" v-if="loading">
13
+ <u-loading-icon size="24" />
14
+ </div>
15
+ <chat-message-item v-for="(message, index) in messages" :key="message.id || index"
16
+ :position="message.position" :type="message.type" :content="message.content" :avatar="message.avatar"
17
+ :show-avatar="shouldShowAvatar(message, index)" :status="message.status" :duration="message.duration" />
18
+ </div>
19
+
20
+ <div class="u-im-chat-box__input">
21
+ <div class="u-im-chat-box__tools">
22
+ <slot name="input-tools"></slot>
23
+ </div>
24
+ <div class="u-im-chat-box__textarea">
25
+ <u-textarea v-model="inputText" :placeholder="placeholder" :maxlength="maxlength" :auto-height="true"
26
+ @confirm="handleSend" />
27
+ </div>
28
+ <div class="u-im-chat-box__send">
29
+ <u-button type="primary" size="small" @click="handleSend">发送</u-button>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { ref, nextTick, watch, onMounted } from 'vue'
37
+ import ChatMessageItem from '../chat-message-item/index.vue'
38
+
39
+ interface Message {
40
+ id?: string | number
41
+ position: 'left' | 'right'
42
+ type: 'text' | 'image' | 'voice' | 'file'
43
+ content: string
44
+ avatar?: string
45
+ status?: 'sending' | 'success' | 'failed'
46
+ duration?: number
47
+ timestamp?: number
48
+ }
49
+
50
+ interface ChatBoxProps {
51
+ title?: string
52
+ messages?: Message[]
53
+ placeholder?: string
54
+ maxlength?: number
55
+ loading?: boolean
56
+ }
57
+
58
+ const props = withDefaults(defineProps<ChatBoxProps>(), {
59
+ title: '聊天',
60
+ messages: () => [],
61
+ placeholder: '请输入消息...',
62
+ maxlength: 1000,
63
+ loading: false
64
+ })
65
+
66
+ const emit = defineEmits<{
67
+ send: [message: string]
68
+ loadMore: []
69
+ }>()
70
+
71
+ const inputText = ref('')
72
+ const messagesRef = ref<HTMLElement>()
73
+
74
+ const handleSend = () => {
75
+ if (inputText.value.trim()) {
76
+ emit('send', inputText.value.trim())
77
+ inputText.value = ''
78
+ }
79
+ }
80
+
81
+ const shouldShowAvatar = (message: Message, index: number): boolean => {
82
+ if (index === 0) return true
83
+ const prevMessage = props.messages[index - 1]
84
+ if (prevMessage.position !== message.position ||
85
+ prevMessage.avatar !== message.avatar ||
86
+ (message.timestamp && prevMessage.timestamp &&
87
+ message.timestamp - prevMessage.timestamp > 300000)) // 5分钟
88
+ {
89
+ return true
90
+ }
91
+ return false
92
+ }
93
+
94
+ const scrollToBottom = () => {
95
+ nextTick(() => {
96
+ if (messagesRef.value) {
97
+ messagesRef.value.scrollTop = messagesRef.value.scrollHeight
98
+ }
99
+ })
100
+ }
101
+
102
+ watch(() => props.messages, () => {
103
+ scrollToBottom()
104
+ }, { deep: true })
105
+
106
+ onMounted(() => {
107
+ scrollToBottom()
108
+ })
109
+ </script>
110
+
111
+ <style scoped>
112
+ .u-im-chat-box {
113
+ display: flex;
114
+ flex-direction: column;
115
+ height: 600px;
116
+ border: 1px solid #e4e7ed;
117
+ border-radius: 8px;
118
+ overflow: hidden;
119
+ }
120
+
121
+ .u-im-chat-box__header {
122
+ display: flex;
123
+ justify-content: space-between;
124
+ align-items: center;
125
+ padding: 12px 16px;
126
+ background-color: #f5f7fa;
127
+ border-bottom: 1px solid #e4e7ed;
128
+ }
129
+
130
+ .u-im-chat-box__title {
131
+ font-size: 16px;
132
+ font-weight: 500;
133
+ color: #303133;
134
+ }
135
+
136
+ .u-im-chat-box__messages {
137
+ flex: 1;
138
+ padding: 16px;
139
+ overflow-y: auto;
140
+ background-color: #fafafa;
141
+ }
142
+
143
+ .u-im-chat-box__loading {
144
+ display: flex;
145
+ justify-content: center;
146
+ padding: 16px;
147
+ }
148
+
149
+ .u-im-chat-box__input {
150
+ border-top: 1px solid #e4e7ed;
151
+ background-color: #fff;
152
+ }
153
+
154
+ .u-im-chat-box__tools {
155
+ padding: 8px 16px;
156
+ border-bottom: 1px solid #e4e7ed;
157
+ }
158
+
159
+ .u-im-chat-box__textarea {
160
+ padding: 12px 16px;
161
+ }
162
+
163
+ .u-im-chat-box__send {
164
+ padding: 0 16px 12px;
165
+ text-align: right;
166
+ }
167
+ </style>
@@ -0,0 +1,105 @@
1
+ <!-- packages/chat-item/index.vue -->
2
+ <template>
3
+ <div class="u-im-chat-item" :class="[`u-im-chat-item--${type}`]" @click="handleClick">
4
+ <div class="u-im-chat-item__avatar">
5
+ <u-avatar :src="avatar" size="40" />
6
+ </div>
7
+ <div class="u-im-chat-item__content">
8
+ <div class="u-im-chat-item__header">
9
+ <span class="u-im-chat-item__name">{{ name }}</span>
10
+ <span class="u-im-chat-item__time">{{ time }}</span>
11
+ </div>
12
+ <div class="u-im-chat-item__message">
13
+ <span class="u-im-chat-item__text">{{ lastMessage }}</span>
14
+ <u-badge v-if="unreadCount > 0" :value="unreadCount" max="99" class="u-im-chat-item__badge" />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { withDefaults } from 'vue'
22
+
23
+ interface ChatItemProps {
24
+ type?: 'default' | 'group'
25
+ avatar?: string
26
+ name: string
27
+ time: string
28
+ lastMessage: string
29
+ unreadCount?: number
30
+ }
31
+
32
+ // const props =
33
+ withDefaults(defineProps<ChatItemProps>(), {
34
+ type: 'default',
35
+ avatar: '',
36
+ unreadCount: 0
37
+ })
38
+
39
+ const emit = defineEmits<{
40
+ click: [event: MouseEvent]
41
+ }>()
42
+
43
+ const handleClick = (event: MouseEvent) => {
44
+ emit('click', event)
45
+ }
46
+ </script>
47
+
48
+ <style scoped>
49
+ .u-im-chat-item {
50
+ display: flex;
51
+ padding: 12px 16px;
52
+ cursor: pointer;
53
+ transition: background-color 0.3s;
54
+ }
55
+
56
+ .u-im-chat-item:hover {
57
+ background-color: #f5f7fa;
58
+ }
59
+
60
+ .u-im-chat-item__avatar {
61
+ margin-right: 12px;
62
+ }
63
+
64
+ .u-im-chat-item__content {
65
+ flex: 1;
66
+ min-width: 0;
67
+ }
68
+
69
+ .u-im-chat-item__header {
70
+ display: flex;
71
+ justify-content: space-between;
72
+ align-items: center;
73
+ margin-bottom: 4px;
74
+ }
75
+
76
+ .u-im-chat-item__name {
77
+ font-size: 16px;
78
+ font-weight: 500;
79
+ color: #303133;
80
+ }
81
+
82
+ .u-im-chat-item__time {
83
+ font-size: 12px;
84
+ color: #909399;
85
+ }
86
+
87
+ .u-im-chat-item__message {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ align-items: center;
91
+ }
92
+
93
+ .u-im-chat-item__text {
94
+ font-size: 14px;
95
+ color: #606266;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ white-space: nowrap;
99
+ flex: 1;
100
+ }
101
+
102
+ .u-im-chat-item__badge {
103
+ margin-left: 8px;
104
+ }
105
+ </style>
@@ -0,0 +1,129 @@
1
+ <!-- packages/chat-message-item/index.vue -->
2
+ <template>
3
+ <div class="u-im-message-item" :class="[`u-im-message-item--${position}`]">
4
+ <div class="u-im-message-item__avatar" v-if="showAvatar">
5
+ <u-avatar :src="avatar" size="32" />
6
+ </div>
7
+ <div class="u-im-message-item__content">
8
+ <div class="u-im-message-item__bubble" :class="[`u-im-message-item__bubble--${type}`]">
9
+ <div v-if="type === 'text'" class="u-im-message-item__text">
10
+ {{ content }}
11
+ </div>
12
+ <div v-else-if="type === 'image'" class="u-im-message-item__image">
13
+ <u-image :src="content" width="200" height="150" mode="aspectFill" />
14
+ </div>
15
+ <div v-else-if="type === 'voice'" class="u-im-message-item__voice">
16
+ <u-icon name="play-circle" size="20" />
17
+ <span class="u-im-message-item__duration">{{ duration }}''</span>
18
+ </div>
19
+ </div>
20
+ <div class="u-im-message-item__status" v-if="position === 'right'">
21
+ <u-icon v-if="status === 'sending'" name="loading" size="16" color="#909399" />
22
+ <u-icon v-else-if="status === 'success'" name="checkmark" size="16" color="#67C23A" />
23
+ <u-icon v-else-if="status === 'failed'" name="close" size="16" color="#F56C6C" />
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { withDefaults } from 'vue'
31
+
32
+ type MessageType = 'text' | 'image' | 'voice' | 'file'
33
+ type MessagePosition = 'left' | 'right'
34
+ type MessageStatus = 'sending' | 'success' | 'failed'
35
+
36
+ interface ChatMessageItemProps {
37
+ position?: MessagePosition
38
+ type?: MessageType
39
+ content: string
40
+ avatar?: string
41
+ showAvatar?: boolean
42
+ status?: MessageStatus
43
+ duration?: number
44
+ }
45
+
46
+ // const props =
47
+ withDefaults(defineProps<ChatMessageItemProps>(), {
48
+ position: 'left',
49
+ type: 'text',
50
+ showAvatar: true,
51
+ status: 'success',
52
+ duration: 0
53
+ })
54
+ </script>
55
+
56
+ <style scoped>
57
+ .u-im-message-item {
58
+ display: flex;
59
+ margin-bottom: 16px;
60
+ }
61
+
62
+ .u-im-message-item--left {
63
+ justify-content: flex-start;
64
+ }
65
+
66
+ .u-im-message-item--right {
67
+ justify-content: flex-end;
68
+ }
69
+
70
+ .u-im-message-item--right .u-im-message-item__content {
71
+ flex-direction: row-reverse;
72
+ }
73
+
74
+ .u-im-message-item__avatar {
75
+ margin: 0 8px;
76
+ align-self: flex-end;
77
+ }
78
+
79
+ .u-im-message-item__content {
80
+ display: flex;
81
+ align-items: flex-end;
82
+ max-width: 70%;
83
+ }
84
+
85
+ .u-im-message-item__bubble {
86
+ padding: 8px 12px;
87
+ border-radius: 8px;
88
+ position: relative;
89
+ }
90
+
91
+ .u-im-message-item__bubble--text {
92
+ background-color: #f0f2f5;
93
+ color: #303133;
94
+ }
95
+
96
+ .u-im-message-item--right .u-im-message-item__bubble--text {
97
+ background-color: #409eff;
98
+ color: #fff;
99
+ }
100
+
101
+ .u-im-message-item__image {
102
+ border-radius: 4px;
103
+ overflow: hidden;
104
+ }
105
+
106
+ .u-im-message-item__voice {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 8px;
110
+ padding: 8px 16px;
111
+ background-color: #f0f2f5;
112
+ border-radius: 16px;
113
+ }
114
+
115
+ .u-im-message-item--right .u-im-message-item__voice {
116
+ background-color: #409eff;
117
+ color: #fff;
118
+ }
119
+
120
+ .u-im-message-item__duration {
121
+ font-size: 12px;
122
+ }
123
+
124
+ .u-im-message-item__status {
125
+ margin: 0 4px;
126
+ display: flex;
127
+ align-items: center;
128
+ }
129
+ </style>
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { App } from 'vue'
2
+ import ChatItem from './components/chat-item/index.vue'
3
+ import ChatMessageItem from './components/chat-message-item/index.vue'
4
+ import ChatBox from './components/chat-box/index.vue'
5
+
6
+ // 导出类型
7
+ export * from './types'
8
+
9
+ // 导出工具函数
10
+ export * from './utils'
11
+
12
+ // 重要:为组件添加名称,以支持开发环境
13
+ ChatItem.name = 'ChatItem'
14
+ ChatMessageItem.name = 'ChatMessageItem'
15
+ ChatBox.name = 'ChatBox'
16
+
17
+ const components = [
18
+ ChatItem,
19
+ ChatMessageItem,
20
+ ChatBox
21
+ ]
22
+
23
+ const install = (app: App): void => {
24
+ components.forEach(component => {
25
+ app.component(component.name || '', component)
26
+ })
27
+ }
28
+
29
+ export {
30
+ ChatItem,
31
+ ChatMessageItem,
32
+ ChatBox,
33
+ install
34
+ }
35
+
36
+ export default {
37
+ install
38
+ }
@@ -0,0 +1,183 @@
1
+ import type { RTC_STATE, MESSAGE_TYPE } from '../utils/enums'
2
+
3
+ export * from './user'
4
+ export * from './recorder'
5
+
6
+ /**
7
+ * 通话模式类型
8
+ */
9
+ export type RtcMode = 'audio' | 'video' | 'voice'
10
+
11
+ /**
12
+ * RTC 信息接口
13
+ */
14
+ export interface RtcInfo {
15
+ friend: Friend
16
+ mode: RtcMode
17
+ state: RTC_STATE
18
+ isHost?: boolean
19
+ }
20
+
21
+ /**
22
+ * 聊天
23
+ */
24
+ export interface Chat {
25
+ id?: string | number
26
+ targetId: number
27
+ type: 'PRIVATE' | 'GROUP'
28
+ showName: string
29
+ headImage: string
30
+ isDnd: boolean
31
+ lastContent: string
32
+ lastSendTime?: number
33
+ unreadCount: number
34
+ hotMinIdx: number
35
+ readedMessageIdx: number
36
+ messages: Message[]
37
+ atMe: boolean
38
+ atAll: boolean
39
+ stored: boolean
40
+ delete: boolean
41
+ lastTimeTip?: number
42
+ sendNickName?: string
43
+ }
44
+
45
+ /**
46
+ * 消息
47
+ */
48
+ export interface Message {
49
+ id?: number
50
+ tmpId?: number
51
+ type: number
52
+ content: string
53
+ sendTime?: number
54
+ selfSend?: boolean
55
+ status?: number
56
+ sendNickName?: string
57
+ atUserIds?: number[]
58
+ sendId?: number
59
+ recvId?: number
60
+ groupId?: number
61
+ receipt?: boolean
62
+ receiptOk?: boolean
63
+ readedCount?: number
64
+ quoteMessage?: Message
65
+ fileId?: string
66
+ }
67
+
68
+ /**
69
+ * 好友信息接口
70
+ */
71
+ export interface Friend {
72
+ id: number
73
+ nickName: string
74
+ headImage?: string
75
+ online: boolean
76
+ onlineWeb: boolean
77
+ onlineApp: boolean
78
+ isDnd: boolean
79
+ deleted?: boolean
80
+ remarkName?: string
81
+ gender?: number
82
+ signature?: string
83
+ remarkNickName?: string
84
+ showNickName?: string
85
+ [key: string]: any
86
+ }
87
+
88
+ /**
89
+ * 在线终端信息接口
90
+ */
91
+ export interface OnlineTerminal {
92
+ userId: string | number
93
+ terminals: number[]
94
+ }
95
+
96
+ /**
97
+ * 菜单项
98
+ */
99
+ export interface MenuItem {
100
+ key: string
101
+ name: string
102
+ icon?: string
103
+ color?: string
104
+ [key: string]: any
105
+ }
106
+
107
+ export interface WebRTCMessage {
108
+ type: number
109
+ content: string
110
+ sendId: number
111
+ selfSend?: boolean
112
+ }
113
+
114
+ export interface GroupMember {
115
+ id?: number
116
+ userId: number
117
+ showNickName: string
118
+ headImage?: string
119
+ quit?: boolean
120
+ [key: string]: any
121
+ }
122
+
123
+ /**
124
+ * 群组信息接口
125
+ */
126
+ export interface Group {
127
+ id: number
128
+ ownerId: number
129
+ isBanned?: boolean
130
+ reason?: string
131
+ name: string
132
+ headImage: string
133
+ isDnd: boolean
134
+ quit?: boolean
135
+ topMessage?: string
136
+ memberCount?: number
137
+ createTime?: number
138
+ showGroupName: string
139
+ headImageThumb?: string
140
+ remarkGroupName?: string
141
+ remarkNickName?: string
142
+ notice?: string
143
+ [key: string]: any
144
+ }
145
+
146
+ /**
147
+ * 上传图片相应数据
148
+ */
149
+ export interface UploadImageResponse {
150
+ originUrl: string
151
+ thumbUrl: string
152
+ [key: string]: any
153
+ }
154
+
155
+ /**
156
+ * 发送消息项
157
+ */
158
+ export interface SubmitItem {
159
+ type: MESSAGE_TYPE // 'text' | 'image' | 'file'
160
+ content: string | ImageItem | FileItem
161
+ atUserIds?: number[]
162
+ }
163
+
164
+ export interface ImageItem {
165
+ fileId: number
166
+ file: File
167
+ url: string
168
+ }
169
+
170
+ export interface FileItem {
171
+ fileId: number
172
+ file: File
173
+ }
174
+
175
+ export interface ApiResponse<T = any> {
176
+ data?: T
177
+ code?: number
178
+ message?: string
179
+ success?: boolean
180
+ timestamp?: number
181
+ path?: string
182
+ }
183
+
@@ -0,0 +1,21 @@
1
+ // -----------------------------
2
+ // Recorder
3
+ // -----------------------------
4
+ export interface RecorderError {
5
+ errMsg: string;
6
+ }
7
+
8
+ export interface UploadRecorderFileResponse {
9
+ code: number;
10
+ data: string;
11
+ message?: string;
12
+ }
13
+
14
+ export interface UploadRecorderFileResult {
15
+ duration: number;
16
+ url: string;
17
+ }
18
+
19
+ export interface RecorderFile {
20
+ tempFilePath: string;
21
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 用户信息接口
3
+ */
4
+ export interface UserInfo {
5
+ id: number
6
+ nickName: string
7
+ headImage: string
8
+ headImageThumb?: string
9
+ email?: string
10
+ phone?: string
11
+ gender?: number
12
+ signature?: string
13
+ isBanned?: boolean
14
+ reason?: string
15
+ userName?: string
16
+ sex?: number
17
+ online?: boolean
18
+ [key: string]: any
19
+ }
@@ -0,0 +1,32 @@
1
+ export const setToken = (value: string) => {
2
+ uni.setStorageSync("accessToken", value);
3
+ };
4
+
5
+ export const getToken = (): string => {
6
+ return uni.getStorageSync("accessToken") || "";
7
+ };
8
+
9
+ export const setRefreshToken = (value: string) => {
10
+ uni.setStorageSync("refreshToken", value);
11
+ };
12
+
13
+ export const getRefreshToken = (): string => {
14
+ return uni.getStorageSync("refreshToken") || "";
15
+ };
16
+
17
+ export const clearToken = () => {
18
+ uni.removeStorageSync("accessToken");
19
+ uni.removeStorageSync("refreshToken");
20
+ };
21
+
22
+ export const setAuth = (data: any) => {
23
+ setToken(data.accessToken)
24
+ setRefreshToken(data.refreshToken)
25
+ uni.setStorageSync('expired', +new Date() + Number(data.expiresIn) * 60 * 60)
26
+ }
27
+
28
+ export const isAuthed = () => {
29
+ const expired = uni.getStorageSync('expired')
30
+ const token = getToken()
31
+ return !(!expired || expired < +new Date() || !token)
32
+ }