im-ui-mobile 0.1.10 → 0.1.12

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.
@@ -10,7 +10,7 @@
10
10
  </template>
11
11
 
12
12
  <script setup lang="ts">
13
- import { computed } from 'vue';
13
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
14
14
 
15
15
  interface Props {
16
16
  id?: number;
@@ -35,7 +35,7 @@
35
35
  </template>
36
36
 
37
37
  <script setup lang="ts">
38
- import { computed, useSlots } from 'vue'
38
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
39
39
 
40
40
  // 定义Props
41
41
  interface BadgeProps {
@@ -57,7 +57,7 @@
57
57
  </template>
58
58
 
59
59
  <script setup lang="ts">
60
- import { computed } from 'vue'
60
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
61
61
  import { validator } from '../../index'
62
62
 
63
63
  // 定义类型
@@ -91,7 +91,7 @@
91
91
  </template>
92
92
 
93
93
  <script setup lang="ts">
94
- import { computed } from 'vue'
94
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
95
95
  import type { CSSProperties } from 'vue'
96
96
  import ImButton from '../im-button/im-button.vue'
97
97
 
@@ -96,7 +96,7 @@
96
96
  </template>
97
97
 
98
98
  <script setup lang="ts">
99
- import { computed, useSlots } from 'vue'
99
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
100
100
 
101
101
  // 定义类型
102
102
  type CellType = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
@@ -45,7 +45,7 @@
45
45
  </template>
46
46
 
47
47
  <script setup lang="ts">
48
- import { ref, computed, watch } from 'vue'
48
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
49
49
  import ImCell from '../im-cell/im-cell.vue'
50
50
 
51
51
  // 定义 Props 接口
@@ -15,7 +15,7 @@
15
15
  <view class="chat-content">
16
16
  <view class="chat-at-text">{{ atText }}</view>
17
17
  <view class="chat-send-name" v-if="isShowSendName">{{ chat.sendNickName + ':&nbsp;' }}</view>
18
- <view v-if="!isTextMessage" class="chat-content-text">{{ chat.lastContent }}</view>
18
+ <view v-if="!isTextMessage" class="chat-content-text">{{ chat.lastMessage }}</view>
19
19
  <rich-text v-else class="chat-content-text" :nodes="nodesText"></rich-text>
20
20
  <view v-if="chat.isDnd" class="icon iconfont icon-dnd"></view>
21
21
  <im-badge v-if="chat.unreadCount > 0" size="small" :max="99" :value="chat.unreadCount" />
@@ -25,7 +25,7 @@
25
25
  </template>
26
26
 
27
27
  <script setup lang="ts">
28
- import { computed } from 'vue'
28
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
29
29
  import ImAvatar from '../im-avatar/im-avatar.vue'
30
30
  import ImBadge from '../im-badge/im-badge.vue'
31
31
  import { datetime, dom, messageType, emoji } from '../../index'
@@ -82,7 +82,7 @@ const isTextMessage = computed(() => {
82
82
  });
83
83
 
84
84
  const nodesText = computed(() => {
85
- const text = dom.html2Escape(props.chat?.lastContent || '');
85
+ const text = dom.html2Escape(props.chat?.lastMessage || '');
86
86
  return emoji.transform(text, 'emoji-small');
87
87
  });
88
88
 
@@ -1,22 +1,17 @@
1
1
  <template>
2
- <view
3
- class="im-col"
4
- :class="[
5
- `im-col--span-${span}`,
6
- `im-col--offset-${offset}`,
7
- `im-col--order-${order}`,
8
- `im-col--align-self-${alignSelf}`,
9
- { 'im-col--gutter': hasGutter }
10
- ]"
11
- :style="colStyle"
12
- @tap="handleClick"
13
- >
2
+ <view class="im-col" :class="[
3
+ `im-col--span-${span}`,
4
+ `im-col--offset-${offset}`,
5
+ `im-col--order-${order}`,
6
+ `im-col--align-self-${alignSelf}`,
7
+ { 'im-col--gutter': hasGutter }
8
+ ]" :style="colStyle" @tap="handleClick">
14
9
  <slot></slot>
15
10
  </view>
16
11
  </template>
17
12
 
18
13
  <script setup lang="ts">
19
- import { computed, inject } from 'vue'
14
+ import { ref, computed, watch, nextTick, useSlots, inject } from 'vue'
20
15
 
21
16
  // 定义 Props
22
17
  interface Props {
@@ -26,14 +21,14 @@ interface Props {
26
21
  order?: number | string
27
22
  alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'stretch'
28
23
  flex?: string
29
-
24
+
30
25
  // 响应式配置
31
26
  xs?: number | string
32
27
  sm?: number | string
33
28
  md?: number | string
34
29
  lg?: number | string
35
30
  xl?: number | string
36
-
31
+
37
32
  // 样式配置
38
33
  width?: string
39
34
  height?: string
@@ -42,7 +37,7 @@ interface Props {
42
37
  bgColor?: string
43
38
  borderColor?: string
44
39
  borderRadius?: string
45
-
40
+
46
41
  // 点击事件
47
42
  clickable?: boolean
48
43
  }
@@ -59,13 +54,13 @@ const props = withDefaults(defineProps<Props>(), {
59
54
  order: 0,
60
55
  alignSelf: 'auto',
61
56
  flex: 'none',
62
-
57
+
63
58
  xs: 0,
64
59
  sm: 0,
65
60
  md: 0,
66
61
  lg: 0,
67
62
  xl: 0,
68
-
63
+
69
64
  width: 'auto',
70
65
  height: 'auto',
71
66
  margin: '0',
@@ -73,7 +68,7 @@ const props = withDefaults(defineProps<Props>(), {
73
68
  bgColor: 'transparent',
74
69
  borderColor: 'transparent',
75
70
  borderRadius: '0',
76
-
71
+
77
72
  clickable: false
78
73
  })
79
74
 
@@ -107,28 +102,28 @@ const colStyle = computed(() => {
107
102
  order: props.order,
108
103
  alignSelf: props.alignSelf
109
104
  }
110
-
105
+
111
106
  // 设置 flex
112
107
  if (props.flex !== 'none') {
113
108
  style.flex = props.flex
114
109
  }
115
-
110
+
116
111
  // 设置边框
117
112
  if (props.borderColor !== 'transparent') {
118
113
  style.borderWidth = '2rpx'
119
114
  style.borderStyle = 'solid'
120
115
  }
121
-
116
+
122
117
  // 设置 gutter
123
118
  if (hasGutter.value) {
124
- const gutter = typeof rowContext.gutter === 'number'
125
- ? `${rowContext.gutter}rpx`
119
+ const gutter = typeof rowContext.gutter === 'number'
120
+ ? `${rowContext.gutter}rpx`
126
121
  : rowContext.gutter
127
-
122
+
128
123
  style.paddingLeft = `calc(${gutter} / 2)`
129
124
  style.paddingRight = `calc(${gutter} / 2)`
130
125
  }
131
-
126
+
132
127
  return style
133
128
  })
134
129
 
@@ -144,43 +139,43 @@ const handleClick = (event: any) => {
144
139
  .im-col {
145
140
  box-sizing: border-box;
146
141
  position: relative;
147
-
142
+
148
143
  // 基本 span 类
149
144
  @for $i from 1 through 24 {
150
145
  &--span-#{$i} {
151
146
  width: calc(100% * $i / 24);
152
147
  }
153
-
148
+
154
149
  &--offset-#{$i} {
155
150
  margin-left: calc(100% * $i / 24);
156
151
  }
157
-
152
+
158
153
  &--order-#{$i} {
159
154
  order: $i;
160
155
  }
161
156
  }
162
-
157
+
163
158
  // 对齐方式
164
159
  &--align-self-auto {
165
160
  align-self: auto;
166
161
  }
167
-
162
+
168
163
  &--align-self-start {
169
164
  align-self: flex-start;
170
165
  }
171
-
166
+
172
167
  &--align-self-end {
173
168
  align-self: flex-end;
174
169
  }
175
-
170
+
176
171
  &--align-self-center {
177
172
  align-self: center;
178
173
  }
179
-
174
+
180
175
  &--align-self-stretch {
181
176
  align-self: stretch;
182
177
  }
183
-
178
+
184
179
  // 点击效果
185
180
  &:active {
186
181
  &.im-col--clickable {
@@ -13,7 +13,7 @@
13
13
  </template>
14
14
 
15
15
  <script setup lang="ts">
16
- import { ref } from 'vue'
16
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
17
17
 
18
18
  interface Props {
19
19
  items?: any[];
@@ -96,7 +96,7 @@
96
96
  </template>
97
97
 
98
98
  <script setup lang="ts">
99
- import { ref, computed, watch, nextTick } from 'vue'
99
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
100
100
  import type { Modal as ModalType } from 'im-ui-mobile'
101
101
  import ImModal from '../im-modal/im-modal.vue'
102
102
 
@@ -22,11 +22,11 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
+ import { ref, computed, watch, nextTick, useSlots } from 'vue'
25
26
  import ImAvatar from '../im-avatar/im-avatar.vue'
26
27
  import ImPopup from '../im-popup/im-popup.vue'
27
28
  import ImModal from '../im-modal/im-modal.vue'
28
- import { ref } from 'vue';
29
- import { UserInfo } from '../../libs';
29
+ import { UserInfo } from '../../libs'
30
30
 
31
31
  interface Props {
32
32
  groupId?: number;
@@ -2,94 +2,62 @@
2
2
  <view class="im-parse" :class="customClass" :style="customStyle">
3
3
  <!-- 解析模式 -->
4
4
  <template v-if="mode === 'html' && content">
5
- <rich-text v-if="useRichText"
6
- :id="richTextId"
7
- :nodes="dom.nodesToHtml(parsedNodes)"
8
- />
9
- <view
10
- v-else
11
- class="im-parse__html"
12
- v-html="content"
13
- @tap="handleHtmlTap"
14
- />
5
+ <rich-text v-if="useRichText" :id="richTextId" :nodes="dom.nodesToHtml(parsedNodes)" />
6
+ <view v-else class="im-parse__html" v-html="content" @tap="handleHtmlTap" />
15
7
  </template>
16
-
8
+
17
9
  <!-- 文本模式 -->
18
10
  <template v-else-if="mode === 'text' && content">
19
- <text
20
- class="im-parse__text"
21
- :style="textStyle"
22
- @tap="handleTextTap"
23
- >
11
+ <text class="im-parse__text" :style="textStyle" @tap="handleTextTap">
24
12
  {{ processedText }}
25
13
  </text>
26
14
  </template>
27
-
15
+
28
16
  <!-- Markdown 模式 -->
29
17
  <template v-else-if="mode === 'markdown' && content">
30
18
  <view class="im-parse__markdown">
31
19
  <template v-for="(node, index) in markdownNodes" :key="index">
32
20
  <!-- 标题 -->
33
- <view
34
- v-if="node.type === 'heading'"
35
- class="im-parse__heading"
36
- :class="`im-parse__heading--${node.depth}`"
37
- >
21
+ <view v-if="node.type === 'heading'" class="im-parse__heading" :class="`im-parse__heading--${node.depth}`">
38
22
  <text>{{ node.text }}</text>
39
23
  </view>
40
-
24
+
41
25
  <!-- 段落 -->
42
- <view
43
- v-else-if="node.type === 'paragraph'"
44
- class="im-parse__paragraph"
45
- >
26
+ <view v-else-if="node.type === 'paragraph'" class="im-parse__paragraph">
46
27
  <text>{{ node.text }}</text>
47
28
  </view>
48
-
29
+
49
30
  <!-- 列表 -->
50
- <view
51
- v-else-if="node.type === 'list'"
52
- class="im-parse__list"
53
- :class="{ 'im-parse__list--ordered': node.ordered }"
54
- >
55
- <view
56
- v-for="(item, itemIndex) in node.items"
57
- :key="itemIndex"
58
- class="im-parse__list-item"
59
- >
31
+ <view v-else-if="node.type === 'list'" class="im-parse__list"
32
+ :class="{ 'im-parse__list--ordered': node.ordered }">
33
+ <view v-for="(item, itemIndex) in node.items" :key="itemIndex" class="im-parse__list-item">
60
34
  <view class="im-parse__list-marker">
61
35
  {{ node.ordered ? `${Number(itemIndex) + 1}.` : '•' }}
62
36
  </view>
63
37
  <text class="im-parse__list-text">{{ item }}</text>
64
38
  </view>
65
39
  </view>
66
-
40
+
67
41
  <!-- 引用 -->
68
- <view
69
- v-else-if="node.type === 'blockquote'"
70
- class="im-parse__blockquote"
71
- >
42
+ <view v-else-if="node.type === 'blockquote'" class="im-parse__blockquote">
72
43
  <text>{{ node.text }}</text>
73
44
  </view>
74
-
45
+
75
46
  <!-- 代码块 -->
76
- <view
77
- v-else-if="node.type === 'code'"
78
- class="im-parse__code-block"
79
- >
47
+ <view v-else-if="node.type === 'code'" class="im-parse__code-block">
80
48
  <text class="im-parse__code-text">{{ node.text }}</text>
81
49
  </view>
82
50
  </template>
83
51
  </view>
84
52
  </template>
85
-
53
+
86
54
  <!-- JSON 模式 -->
87
55
  <template v-else-if="mode === 'json' && content">
88
56
  <view class="im-parse__json">
89
57
  <pre class="im-parse__json-pre">{{ formattedJson }}</pre>
90
58
  </view>
91
59
  </template>
92
-
60
+
93
61
  <!-- 默认插槽 -->
94
62
  <slot v-if="!content" name="default"></slot>
95
63
  </view>
@@ -97,7 +65,7 @@
97
65
 
98
66
  <script setup lang="ts">
99
67
  import { ref, computed, watch } from 'vue'
100
- import type{LinkData,ParseOptions} from "../../types/components/parse";
68
+ import type { LinkData, ParseOptions } from "../../types/components/parse";
101
69
  import { emoji, dom } from '../../index'
102
70
 
103
71
  // 定义 Props
@@ -105,19 +73,19 @@ interface Props {
105
73
  // 内容
106
74
  content?: string
107
75
  modelValue?: string
108
-
76
+
109
77
  // 解析选项
110
78
  options?: ParseOptions
111
79
 
112
80
  // 解析模式
113
81
  mode?: 'html' | 'text' | 'markdown' | 'json'
114
82
  encoding?: 'utf-8' | 'base64' | 'url'
115
-
83
+
116
84
  // 通用配置
117
85
  maxLength?: number
118
86
  ellipsis?: string
119
87
  lines?: number
120
-
88
+
121
89
  // HTML 配置
122
90
  useRichText?: boolean
123
91
  space?: 'ensp' | 'emsp' | 'nbsp'
@@ -125,19 +93,19 @@ interface Props {
125
93
  allowTags?: string[]
126
94
  allowAttributes?: Record<string, string[]>
127
95
  allowDomains?: string[]
128
-
96
+
129
97
  // 样式配置
130
98
  customClass?: string
131
99
  customStyle?: string | Record<string, string>
132
100
  textStyle?: string | Record<string, string>
133
-
101
+
134
102
  // 功能配置
135
103
  autolink?: boolean
136
104
  emoji?: boolean
137
105
  highlight?: boolean
138
106
  lazyLoad?: boolean
139
107
  errorFallback?: string
140
-
108
+
141
109
  // 事件配置
142
110
  preventDefault?: boolean
143
111
  }
@@ -146,7 +114,7 @@ interface Props {
146
114
  interface Emits {
147
115
  (e: 'update:modelValue', value: string): void
148
116
  (e: 'click', event: any): void
149
- (e:'link-click',link: LinkData, event?: Event):void
117
+ (e: 'link-click', link: LinkData, event?: Event): void
150
118
  (e: 'linkpress', url: string): void
151
119
  (e: 'imageerror', src: string, error: Error): void
152
120
  (e: 'imageload', src: string): void
@@ -158,7 +126,7 @@ interface Emits {
158
126
  const props = withDefaults(defineProps<Props>(), {
159
127
  content: '',
160
128
  modelValue: '',
161
-
129
+
162
130
  options: () => ({
163
131
  enableLinks: true,
164
132
  openExternalInNewWindow: true,
@@ -168,39 +136,39 @@ const props = withDefaults(defineProps<Props>(), {
168
136
 
169
137
  mode: 'html',
170
138
  encoding: 'utf-8',
171
-
139
+
172
140
  maxLength: 0,
173
141
  ellipsis: '...',
174
142
  lines: 0,
175
-
143
+
176
144
  useRichText: true,
177
145
  space: 'ensp',
178
146
  filterTags: () => ['script', 'style', 'iframe', 'object', 'embed'],
179
147
  allowTags: () => [],
180
148
  allowAttributes: () => ({}),
181
149
  allowDomains: () => [],
182
-
150
+
183
151
  customClass: '',
184
152
  customStyle: () => ({}),
185
153
  textStyle: () => ({}),
186
-
154
+
187
155
  autolink: true,
188
156
  emoji: false,
189
157
  highlight: false,
190
158
  lazyLoad: false,
191
159
  errorFallback: '解析失败',
192
-
160
+
193
161
  preventDefault: false
194
162
  })
195
163
 
196
164
  const emit = defineEmits<Emits>()
197
165
 
198
166
  // 响应式状态
199
- const richTextId = ref('richTextId'+new Date().getTime())
167
+ const richTextId = ref('richTextId' + new Date().getTime())
200
168
  const parsedNodes = ref<any[]>([])
201
169
  const markdownNodes = ref<any[]>([])
202
170
  const lastError = ref<Error | null>(null)
203
- const extractedLinks = ref<Array<{id: string, data: LinkData}>>([]) // 提取的链接数据
171
+ const extractedLinks = ref<Array<{ id: string, data: LinkData }>>([]) // 提取的链接数据
204
172
 
205
173
  // 计算内容
206
174
  const computedContent = computed(() => {
@@ -211,7 +179,7 @@ const computedContent = computed(() => {
211
179
  const decodedContent = computed(() => {
212
180
  const content = computedContent.value
213
181
  if (!content) return ''
214
-
182
+
215
183
  try {
216
184
  switch (props.encoding) {
217
185
  case 'base64':
@@ -231,27 +199,27 @@ const decodedContent = computed(() => {
231
199
  // 处理文本内容
232
200
  const processedText = computed(() => {
233
201
  let text = decodedContent.value
234
-
202
+
235
203
  // 处理长度限制
236
204
  if (props.maxLength > 0 && text.length > props.maxLength) {
237
205
  text = text.substring(0, props.maxLength) + props.ellipsis
238
206
  }
239
-
207
+
240
208
  // 自动链接
241
209
  if (props.autolink) {
242
210
  text = autoLink(text)
243
211
  }
244
-
212
+
245
213
  // 表情符号
246
214
  if (props.emoji || (emoji as any).containEmoji(text)) {
247
215
  text = parseEmoji(text)
248
216
  }
249
-
217
+
250
218
  // 语法高亮
251
219
  if (props.highlight) {
252
220
  text = highlightText(text)
253
221
  }
254
-
222
+
255
223
  return text
256
224
  })
257
225
 
@@ -263,7 +231,7 @@ onMounted(() => {
263
231
  // 解析 HTML 节点
264
232
  const parseHtml = async (html: string) => {
265
233
  if (!html) return []
266
-
234
+
267
235
  try {
268
236
  // 简单的 HTML 解析器
269
237
  const nodes = parseHtmlToNodes(html)
@@ -279,7 +247,7 @@ const parseHtml = async (html: string) => {
279
247
  // 解析 Markdown
280
248
  const parseMarkdown = (markdown: string) => {
281
249
  if (!markdown) return []
282
-
250
+
283
251
  try {
284
252
  const nodes = parseMarkdownToNodes(markdown)
285
253
  emit('parse-success', nodes)
@@ -296,7 +264,7 @@ const formattedJson = computed(() => {
296
264
  try {
297
265
  const content = decodedContent.value
298
266
  if (!content) return ''
299
-
267
+
300
268
  const obj = JSON.parse(content)
301
269
  return JSON.stringify(obj, null, 2)
302
270
  } catch (error) {
@@ -307,14 +275,14 @@ const formattedJson = computed(() => {
307
275
  // 简单的 HTML 解析器
308
276
  const parseHtmlToNodes = (html: string): any[] => {
309
277
  const nodes: any[] = []
310
-
278
+
311
279
  // 移除危险标签
312
280
  let safeHtml = html
313
281
  props.filterTags.forEach(tag => {
314
282
  const regex = new RegExp(`<${tag}[^>]*>.*?</${tag}>`, 'gis')
315
283
  safeHtml = safeHtml.replace(regex, '')
316
284
  })
317
-
285
+
318
286
  // 简化的解析逻辑
319
287
  // 实际项目中可以使用更完善的 HTML 解析库
320
288
  const textMatch = safeHtml.match(/>(.*?)</)
@@ -324,7 +292,7 @@ const parseHtmlToNodes = (html: string): any[] => {
324
292
  text: textMatch[1]
325
293
  })
326
294
  }
327
-
295
+
328
296
  return nodes
329
297
  }
330
298
 
@@ -332,12 +300,12 @@ const parseHtmlToNodes = (html: string): any[] => {
332
300
  const parseMarkdownToNodes = (markdown: string): any[] => {
333
301
  const nodes: any[] = []
334
302
  const lines = markdown.split('\n')
335
-
303
+
336
304
  for (let i = 0; i < lines.length; i++) {
337
305
  const line = lines[i].trim()
338
-
306
+
339
307
  if (!line) continue
340
-
308
+
341
309
  // 标题
342
310
  const headingMatch = line.match(/^(#{1,6})\s+(.+)$/)
343
311
  if (headingMatch) {
@@ -348,7 +316,7 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
348
316
  })
349
317
  continue
350
318
  }
351
-
319
+
352
320
  // 列表项
353
321
  const listMatch = line.match(/^[\*\-\+]\s+(.+)$/)
354
322
  if (listMatch) {
@@ -358,7 +326,7 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
358
326
  i++
359
327
  items.push(lines[i].replace(/^[\*\-\+]\s+/, ''))
360
328
  }
361
-
329
+
362
330
  nodes.push({
363
331
  type: 'list',
364
332
  ordered: false,
@@ -366,7 +334,7 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
366
334
  })
367
335
  continue
368
336
  }
369
-
337
+
370
338
  // 数字列表
371
339
  const orderedListMatch = line.match(/^\d+\.\s+(.+)$/)
372
340
  if (orderedListMatch) {
@@ -375,7 +343,7 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
375
343
  i++
376
344
  items.push(lines[i + 1].replace(/^\d+\.\s+/, ''))
377
345
  }
378
-
346
+
379
347
  nodes.push({
380
348
  type: 'list',
381
349
  ordered: true,
@@ -383,7 +351,7 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
383
351
  })
384
352
  continue
385
353
  }
386
-
354
+
387
355
  // 引用
388
356
  if (line.startsWith('> ')) {
389
357
  nodes.push({
@@ -392,18 +360,18 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
392
360
  })
393
361
  continue
394
362
  }
395
-
363
+
396
364
  // 代码块
397
365
  if (line.startsWith('```')) {
398
366
  const language = line.substring(3).trim() || ''
399
367
  const codeLines = []
400
-
368
+
401
369
  while (i + 1 < lines.length && !lines[i + 1].startsWith('```')) {
402
370
  i++
403
371
  codeLines.push(lines[i])
404
372
  }
405
373
  i++ // 跳过结束的 ```
406
-
374
+
407
375
  nodes.push({
408
376
  type: 'code',
409
377
  language,
@@ -411,14 +379,14 @@ const parseMarkdownToNodes = (markdown: string): any[] => {
411
379
  })
412
380
  continue
413
381
  }
414
-
382
+
415
383
  // 普通段落
416
384
  nodes.push({
417
385
  type: 'paragraph',
418
386
  text: line
419
387
  })
420
388
  }
421
-
389
+
422
390
  return nodes
423
391
  }
424
392
 
@@ -440,13 +408,13 @@ const parseEmoji = (text: string): string => {
440
408
  ':*': '😘',
441
409
  '<3': '❤️'
442
410
  }
443
-
411
+
444
412
  let result = text
445
413
  Object.keys(emojiMap).forEach(key => {
446
414
  const k = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
447
415
  result = result.replace(new RegExp(k, 'g'), emojiMap[k])
448
416
  })
449
-
417
+
450
418
  return result
451
419
  }
452
420
 
@@ -455,12 +423,12 @@ const highlightText = (text: string): string => {
455
423
  // 简单的关键词高亮
456
424
  const keywords = ['important', 'urgent', 'note', 'warning', 'info']
457
425
  let result = text
458
-
426
+
459
427
  keywords.forEach(keyword => {
460
428
  const regex = new RegExp(`\\b${keyword}\\b`, 'gi')
461
429
  result = result.replace(regex, `<span class="highlight">${keyword}</span>`)
462
430
  })
463
-
431
+
464
432
  return result
465
433
  }
466
434
 
@@ -469,11 +437,11 @@ const handleLinkPress = (event: any) => {
469
437
  const url = event.detail?.href
470
438
  if (url) {
471
439
  emit('linkpress', url)
472
-
440
+
473
441
  if (props.preventDefault) {
474
442
  return
475
443
  }
476
-
444
+
477
445
  // 检查域名是否允许
478
446
  if (props.allowDomains.length > 0) {
479
447
  try {
@@ -489,7 +457,7 @@ const handleLinkPress = (event: any) => {
489
457
  console.error('解析URL失败:', error)
490
458
  }
491
459
  }
492
-
460
+
493
461
  // 跳转到链接
494
462
  uni.navigateTo({
495
463
  url: `/pages/webview/webview?url=${encodeURIComponent(url)}`
@@ -504,31 +472,31 @@ const handleLinkPress = (event: any) => {
504
472
  const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } => {
505
473
  const links: LinkData[] = []
506
474
  let linkIndex = 0
507
-
475
+
508
476
  // 使用正则表达式匹配 a 标签
509
477
  const linkRegex = /<a\s+(?:[^>]*?\s+)?href=(["'])(.*?)\1[^>]*?>(.*?)<\/a>/gi
510
-
478
+
511
479
  // 替换链接为可点击元素
512
480
  const processedHtml = html.replace(linkRegex, (match, quote, url, text) => {
513
481
  // 清理 URL
514
482
  const cleanUrl = url.trim()
515
483
  const cleanText = text.replace(/<[^>]*>/g, '').trim()
516
-
484
+
517
485
  // 检查是否为外部链接
518
- const isExternal = /^(https?:)?\/\//i.test(cleanUrl) &&
519
- !cleanUrl.includes(location.hostname)
520
-
486
+ const isExternal = /^(https?:)?\/\//i.test(cleanUrl) &&
487
+ !cleanUrl.includes(location.hostname)
488
+
521
489
  const linkData: LinkData = {
522
490
  url: cleanUrl,
523
491
  text: cleanText,
524
492
  isExternal
525
493
  }
526
-
494
+
527
495
  links.push(linkData)
528
-
496
+
529
497
  // 生成唯一的 ID
530
498
  const linkId = `link-${linkIndex++}`
531
-
499
+
532
500
  // 替换为可点击的富文本节点
533
501
  return `<span
534
502
  data-link-id="${linkId}"
@@ -537,7 +505,7 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
537
505
  class="im-parse-link"
538
506
  >${cleanText}</span>`
539
507
  })
540
-
508
+
541
509
  // 解析电子邮件(如果启用)
542
510
  let finalHtml = processedHtml
543
511
  if (props.options?.parseEmail) {
@@ -550,7 +518,7 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
550
518
  isExternal: false
551
519
  }
552
520
  links.push(linkData)
553
-
521
+
554
522
  return `<span
555
523
  data-link-id="${emailId}"
556
524
  data-link-url="mailto:${email}"
@@ -559,7 +527,7 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
559
527
  >${email}</span>`
560
528
  })
561
529
  }
562
-
530
+
563
531
  // 解析电话号码(如果启用)
564
532
  if (props.options?.parsePhone) {
565
533
  const phoneRegex = /(\+?\d[\d\s\-\(\)]{7,}\d)/g
@@ -572,7 +540,7 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
572
540
  isExternal: false
573
541
  }
574
542
  links.push(linkData)
575
-
543
+
576
544
  return `<span
577
545
  data-link-id="${phoneId}"
578
546
  data-link-url="tel:${cleanPhone}"
@@ -581,13 +549,13 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
581
549
  >${phone}</span>`
582
550
  })
583
551
  }
584
-
552
+
585
553
  // 提取链接信息到响应式数组
586
554
  extractedLinks.value = links.map((link, index) => ({
587
555
  id: `link-${index}`,
588
556
  data: link
589
557
  }))
590
-
558
+
591
559
  // 返回解析后的节点(这里简化处理,实际可能需要更复杂的 HTML 解析)
592
560
  return {
593
561
  nodes: [{
@@ -610,12 +578,12 @@ const parseHtmlWithLinks = (html: string): { nodes: any[], links: LinkData[] } =
610
578
  const handleLinkClick = (linkData: LinkData, event?: Event) => {
611
579
  // 触发自定义事件
612
580
  emit('link-click', linkData, event)
613
-
581
+
614
582
  // 调用用户提供的回调
615
583
  if (props.options?.onLinkClick) {
616
584
  props.options.onLinkClick(linkData, event)
617
585
  }
618
-
586
+
619
587
  // 执行默认行为(在 uniapp 中)
620
588
  handleDefaultLinkBehavior(linkData)
621
589
  }
@@ -625,7 +593,7 @@ const handleLinkClick = (linkData: LinkData, event?: Event) => {
625
593
  */
626
594
  const handleDefaultLinkBehavior = (linkData: LinkData) => {
627
595
  const { url, isExternal } = linkData
628
-
596
+
629
597
  // 处理邮件链接
630
598
  if (url.startsWith('mailto:')) {
631
599
  uni.makePhoneCall({
@@ -639,7 +607,7 @@ const handleDefaultLinkBehavior = (linkData: LinkData) => {
639
607
  })
640
608
  return
641
609
  }
642
-
610
+
643
611
  // 处理电话链接
644
612
  if (url.startsWith('tel:')) {
645
613
  uni.makePhoneCall({
@@ -653,7 +621,7 @@ const handleDefaultLinkBehavior = (linkData: LinkData) => {
653
621
  })
654
622
  return
655
623
  }
656
-
624
+
657
625
  // 处理网页链接
658
626
  if (/^(https?:)?\/\//i.test(url)) {
659
627
  // 在 uniapp 中打开网页
@@ -691,7 +659,7 @@ const handleDefaultLinkBehavior = (linkData: LinkData) => {
691
659
  const handleRichTextItemClick = (event: any) => {
692
660
  console.log('click', event)
693
661
  const { dataset } = event.detail || {}
694
-
662
+
695
663
  if (dataset && dataset.linkId) {
696
664
  const linkItem = extractedLinks.value.find(item => item.id === dataset.linkId)
697
665
  if (linkItem) {
@@ -701,7 +669,7 @@ const handleRichTextItemClick = (event: any) => {
701
669
  ...linkItem.data,
702
670
  url: decodedUrl
703
671
  }
704
-
672
+
705
673
  handleLinkClick(linkData, event)
706
674
  }
707
675
  }
@@ -758,7 +726,7 @@ const handleImageLoad = (src: string) => {
758
726
  // 重新解析
759
727
  const reparse = async () => {
760
728
  lastError.value = null
761
-
729
+
762
730
  if (props.mode === 'html') {
763
731
  // parsedNodes.value = await parseHtml(decodedContent.value)
764
732
  initParse()
@@ -823,28 +791,29 @@ defineExpose({
823
791
  width: 100%;
824
792
  word-break: break-word;
825
793
  line-height: 1.6;
826
-
794
+
827
795
  &__html {
796
+
828
797
  // HTML 样式
829
798
  :deep(img) {
830
799
  max-width: 100%;
831
800
  height: auto;
832
801
  vertical-align: middle;
833
802
  }
834
-
803
+
835
804
  :deep(a) {
836
805
  color: #409eff;
837
806
  text-decoration: none;
838
-
807
+
839
808
  &:active {
840
809
  opacity: 0.7;
841
810
  }
842
811
  }
843
-
812
+
844
813
  :deep(p) {
845
814
  margin: 16rpx 0;
846
815
  }
847
-
816
+
848
817
  :deep(h1),
849
818
  :deep(h2),
850
819
  :deep(h3),
@@ -854,33 +823,33 @@ defineExpose({
854
823
  margin: 32rpx 0 16rpx;
855
824
  font-weight: bold;
856
825
  }
857
-
826
+
858
827
  :deep(h1) {
859
828
  font-size: 40rpx;
860
829
  }
861
-
830
+
862
831
  :deep(h2) {
863
832
  font-size: 36rpx;
864
833
  }
865
-
834
+
866
835
  :deep(h3) {
867
836
  font-size: 32rpx;
868
837
  }
869
-
838
+
870
839
  :deep(h4) {
871
840
  font-size: 28rpx;
872
841
  }
873
-
842
+
874
843
  :deep(ul),
875
844
  :deep(ol) {
876
845
  margin: 16rpx 0;
877
846
  padding-left: 48rpx;
878
847
  }
879
-
848
+
880
849
  :deep(li) {
881
850
  margin: 8rpx 0;
882
851
  }
883
-
852
+
884
853
  :deep(blockquote) {
885
854
  margin: 16rpx 0;
886
855
  padding: 16rpx 32rpx;
@@ -888,7 +857,7 @@ defineExpose({
888
857
  border-left: 8rpx solid #ddd;
889
858
  color: #666;
890
859
  }
891
-
860
+
892
861
  :deep(code) {
893
862
  padding: 4rpx 8rpx;
894
863
  background-color: #f5f5f5;
@@ -896,44 +865,44 @@ defineExpose({
896
865
  font-family: Consolas, Monaco, 'Courier New', monospace;
897
866
  font-size: 90%;
898
867
  }
899
-
868
+
900
869
  :deep(pre) {
901
870
  margin: 16rpx 0;
902
871
  padding: 24rpx;
903
872
  background-color: #f5f5f5;
904
873
  border-radius: 8rpx;
905
874
  overflow-x: auto;
906
-
875
+
907
876
  code {
908
877
  padding: 0;
909
878
  background-color: transparent;
910
879
  }
911
880
  }
912
-
881
+
913
882
  :deep(table) {
914
883
  width: 100%;
915
884
  border-collapse: collapse;
916
885
  margin: 16rpx 0;
917
-
886
+
918
887
  th,
919
888
  td {
920
889
  padding: 16rpx;
921
890
  border: 2rpx solid #ddd;
922
891
  text-align: left;
923
892
  }
924
-
893
+
925
894
  th {
926
895
  background-color: #f5f5f5;
927
896
  font-weight: bold;
928
897
  }
929
898
  }
930
899
  }
931
-
900
+
932
901
  &__text {
933
902
  display: block;
934
903
  width: 100%;
935
904
  }
936
-
905
+
937
906
  &__markdown {
938
907
  .highlight {
939
908
  background-color: #fffbe6;
@@ -942,70 +911,70 @@ defineExpose({
942
911
  color: #d48806;
943
912
  }
944
913
  }
945
-
914
+
946
915
  &__heading {
947
916
  margin: 32rpx 0 16rpx;
948
917
  font-weight: bold;
949
-
918
+
950
919
  &--1 {
951
920
  font-size: 40rpx;
952
921
  }
953
-
922
+
954
923
  &--2 {
955
924
  font-size: 36rpx;
956
925
  }
957
-
926
+
958
927
  &--3 {
959
928
  font-size: 32rpx;
960
929
  }
961
-
930
+
962
931
  &--4 {
963
932
  font-size: 28rpx;
964
933
  }
965
-
934
+
966
935
  &--5 {
967
936
  font-size: 26rpx;
968
937
  }
969
-
938
+
970
939
  &--6 {
971
940
  font-size: 24rpx;
972
941
  }
973
942
  }
974
-
943
+
975
944
  &__paragraph {
976
945
  margin: 16rpx 0;
977
946
  }
978
-
947
+
979
948
  &__list {
980
949
  margin: 16rpx 0;
981
-
950
+
982
951
  &--ordered {
983
952
  padding-left: 48rpx;
984
953
  list-style-type: decimal;
985
954
  }
986
-
955
+
987
956
  &:not(.im-parse__list--ordered) {
988
957
  padding-left: 48rpx;
989
958
  list-style-type: disc;
990
959
  }
991
960
  }
992
-
961
+
993
962
  &__list-item {
994
963
  display: flex;
995
964
  align-items: flex-start;
996
965
  margin: 8rpx 0;
997
966
  }
998
-
967
+
999
968
  &__list-marker {
1000
969
  margin-right: 16rpx;
1001
970
  min-width: 32rpx;
1002
971
  text-align: right;
1003
972
  }
1004
-
973
+
1005
974
  &__list-text {
1006
975
  flex: 1;
1007
976
  }
1008
-
977
+
1009
978
  &__blockquote {
1010
979
  margin: 16rpx 0;
1011
980
  padding: 16rpx 32rpx;
@@ -1013,7 +982,7 @@ defineExpose({
1013
982
  border-left: 8rpx solid #ddd;
1014
983
  color: #666;
1015
984
  }
1016
-
985
+
1017
986
  &__code-block {
1018
987
  margin: 16rpx 0;
1019
988
  padding: 24rpx;
@@ -1021,18 +990,18 @@ defineExpose({
1021
990
  border-radius: 8rpx;
1022
991
  overflow-x: auto;
1023
992
  }
1024
-
993
+
1025
994
  &__code-text {
1026
995
  font-family: Consolas, Monaco, 'Courier New', monospace;
1027
996
  font-size: 90%;
1028
997
  white-space: pre;
1029
998
  }
1030
-
999
+
1031
1000
  &__json {
1032
1001
  width: 100%;
1033
1002
  overflow-x: auto;
1034
1003
  }
1035
-
1004
+
1036
1005
  &__json-pre {
1037
1006
  margin: 0;
1038
1007
  padding: 24rpx;
@@ -107,7 +107,7 @@ const loadReadedUser = async (userIds: number[]) => {
107
107
  displayName: '',
108
108
  avatar: '',
109
109
  isDnd: false,
110
- lastContent: '',
110
+ lastMessage: '',
111
111
  unreadCount: 0,
112
112
  hotMinIdx: 0,
113
113
  readedMessageIdx: 0,
@@ -142,6 +142,7 @@
142
142
  </template>
143
143
 
144
144
  <script lang="ts" setup>
145
+ import { ref, computed, watch, nextTick } from 'vue'
145
146
  import type {
146
147
  SkuProps,
147
148
  SkuEmits,
@@ -17,8 +17,7 @@
17
17
  buttonStyle,
18
18
  isMinusDisabled ? disabledButtonStyle : {},
19
19
  isMinusActive ? buttonActiveStyle : {}
20
- ]"
21
- @click="onMinusClick">
20
+ ]" @click="onMinusClick">
22
21
  <im-icon v-if="minusIcon" :name="minusIcon" :size="iconSize" :color="buttonTextColorComputed" />
23
22
  <text v-else-if="minusText" class="im-stepper-button-text" :style="{ color: buttonTextColorComputed }">
24
23
  {{ minusText }}
@@ -57,6 +56,7 @@
57
56
  </template>
58
57
 
59
58
  <script lang="ts" setup>
59
+ import { ref, computed, watch, nextTick, onMounted } from 'vue'
60
60
  import type { StepperProps, StepperEmits } from '../../types/components/stepper'
61
61
  import ImIcon from '../im-icon/im-icon.vue'
62
62
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <!-- 虚拟列表容器 -->
3
- <scroll-view class="virtual-list" :scroll-y="true" :scroll-with-animation="true" :show-scrollbar="false"
3
+ <scroll-view class="im-virtual-list" :scroll-y="true" :scroll-with-animation="true" :show-scrollbar="false"
4
4
  :enhanced="true" :bounces="false" :lower-threshold="lowerThreshold" :upper-threshold="upperThreshold"
5
5
  :scroll-top="scrollTop" :scroll-into-view="scrollIntoViewId" @scroll="handleScroll"
6
6
  @scrolltolower="handleScrollToLower" @scrolltoupper="handleScrollToUpper">
@@ -313,7 +313,7 @@ const clearScrollTimer = () => {
313
313
  const measureContainer = () => {
314
314
  nextTick(() => {
315
315
  const query = uni.createSelectorQuery().in(getCurrentInstance())
316
- query.select('.virtual-list').boundingClientRect((rect: any) => {
316
+ query.select('.im-virtual-list').boundingClientRect((rect: any) => {
317
317
  if (rect && rect.height) {
318
318
  containerHeight.value = rect.height
319
319
  }
@@ -379,7 +379,7 @@ defineExpose({
379
379
  </script>
380
380
 
381
381
  <style lang="scss" scoped>
382
- .virtual-list {
382
+ .im-virtual-list {
383
383
  // min-height: 200rpx;
384
384
  // height: 100vh;
385
385
  }
package/libs/index.ts CHANGED
@@ -25,7 +25,7 @@ export interface Chat {
25
25
  displayName: string
26
26
  avatar: string
27
27
  isDnd: boolean
28
- lastContent: string
28
+ lastMessage: string
29
29
  lastSendTime?: number
30
30
  unreadCount: number
31
31
  hotMinIdx: number
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-ui-mobile",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A Vue3.0 + Typescript instant messaging component library for Uniapp",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -9,6 +9,7 @@ declare interface NavBarAction {
9
9
  disabled?: boolean
10
10
  badge?: string | number
11
11
  data?: any
12
+ click?: Function
12
13
  }
13
14
 
14
15
  declare interface NavBarProps {
@@ -1,6 +1,6 @@
1
1
  import { AllowedComponentProps, VNodeProps } from '../common'
2
2
 
3
- export interface _SkuAttr {
3
+ declare interface _SkuAttr {
4
4
  /** 属性名称 */
5
5
  name: string;
6
6
  /** 属性值列表 */
@@ -11,7 +11,7 @@ export interface _SkuAttr {
11
11
  required?: boolean;
12
12
  }
13
13
 
14
- export interface _SkuAttrValue {
14
+ declare interface _SkuAttrValue {
15
15
  /** 属性值名称 */
16
16
  name: string;
17
17
  /** 属性值ID */
@@ -26,7 +26,7 @@ export interface _SkuAttrValue {
26
26
  previewImgUrl?: string;
27
27
  }
28
28
 
29
- export interface _SkuSpec {
29
+ declare interface _SkuSpec {
30
30
  /** 规格名称 */
31
31
  name: string;
32
32
  /** 规格值列表 */
@@ -37,7 +37,7 @@ export interface _SkuSpec {
37
37
  required?: boolean;
38
38
  }
39
39
 
40
- export interface _SkuValue {
40
+ declare interface _SkuValue {
41
41
  /** 规格值名称 */
42
42
  name: string;
43
43
  /** 规格值ID */
@@ -52,7 +52,7 @@ export interface _SkuValue {
52
52
  previewImgUrl?: string;
53
53
  }
54
54
 
55
- export interface _SkuItem {
55
+ declare interface _SkuItem {
56
56
  /** SKU ID */
57
57
  id: string | number;
58
58
  /** 商品ID */
@@ -75,7 +75,7 @@ export interface _SkuItem {
75
75
  disabled?: boolean;
76
76
  }
77
77
 
78
- export interface _SkuGoodsInfo {
78
+ declare interface _SkuGoodsInfo {
79
79
  /** 商品ID */
80
80
  id: string | number;
81
81
  /** 商品标题 */
@@ -100,7 +100,7 @@ export interface _SkuGoodsInfo {
100
100
  unit?: string;
101
101
  }
102
102
 
103
- export interface _SelectedSku {
103
+ declare interface _SelectedSku {
104
104
  /** 选中的属性组合 */
105
105
  selectedAttrs: Record<string, string>;
106
106
  /** 选中的规格组合 */
package/types/index.d.ts CHANGED
@@ -49,7 +49,7 @@ import validator, {
49
49
  validatePassword
50
50
  } from './utils/validator.d.ts'
51
51
  import { EmojiType, EmojiCategoryType } from './components/emoji-picker'
52
- import * as base64 from "./utils/base64.d.ts";
52
+ import * as base64 from "./utils/base64.d.ts"
53
53
 
54
54
  declare module 'im-ui-mobile' {
55
55
  export function install(): void
@@ -123,4 +123,6 @@ declare module 'im-ui-mobile' {
123
123
  TERMINAL_TYPE,
124
124
  MESSAGE_STATUS,
125
125
  }
126
+
127
+ export * from './components/sku.d.ts'
126
128
  }
@@ -22,7 +22,7 @@ export interface Chat {
22
22
  displayName: string;
23
23
  avatar: string;
24
24
  isDnd: boolean;
25
- lastContent: string;
25
+ lastMessage: string;
26
26
  lastSendTime?: number;
27
27
  unreadCount: number;
28
28
  hotMinIdx: number;