im-ui-mobile 0.1.7 → 0.1.9
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/README.md +28 -5
- package/components/im-button/im-button.vue +3 -9
- package/components/im-card/im-card.vue +1 -2
- package/components/im-cell/im-cell.vue +1 -1
- package/components/im-dialog/im-dialog.vue +1 -0
- package/components/im-emoji-picker/im-emoji-picker.vue +1 -0
- package/components/im-group-member-selector/im-group-member-selector.vue +1 -2
- package/components/im-group-rtc-join/im-group-rtc-join.vue +2 -0
- package/components/im-link/im-link.vue +1 -0
- package/components/im-loading/im-loading.vue +1 -0
- package/components/im-mention-picker/im-mention-picker.vue +3 -1
- package/components/im-message-action/im-message-action.vue +2 -0
- package/components/im-message-list/im-message-list.vue +1 -1
- package/components/im-modal/im-modal.vue +38 -70
- package/components/im-nav-bar/im-nav-bar.vue +1 -0
- package/components/im-read-receipt/im-read-receipt.vue +3 -1
- package/components/im-search/im-search.vue +77 -105
- package/components/im-sku/im-sku.vue +2 -0
- package/components/im-stepper/im-stepper.vue +1 -0
- package/components/im-upload/im-upload.vue +1 -0
- package/components/im-virtual-list/im-virtual-list.vue +1 -0
- package/components/im-voice-input/im-voice-input.vue +1 -0
- package/package.json +1 -1
- package/types/components/button.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
# im-ui-mobile
|
|
1
|
+
# im-ui-mobile 移动端组件库
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/badge/version-1.0.0-blue.svg" alt="version">
|
|
5
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="license">
|
|
6
|
+
<img src="https://img.shields.io/badge/uni--app-3.0+-orange.svg" alt="uniapp">
|
|
7
|
+
<img src="https://img.shields.io/badge/vue-3.0+-brightgreen.svg" alt="vue">
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>一个基于 Vue 3 + TypeScript 的即时通信 Uniapp 组件库</strong>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
## ✨ 特性
|
|
15
|
+
|
|
16
|
+
- 🚀 **基于 Vue 3 Composition API** - 现代化的组件开发方式
|
|
17
|
+
- 📱 **uniapp 多端支持** - 一套代码,多端运行
|
|
18
|
+
- 🎨 **TypeScript 支持** - 完整的类型定义,开发更友好
|
|
19
|
+
- 🎯 **主题定制** - 支持自定义主题和样式
|
|
20
|
+
- 📦 **按需引入** - 支持组件按需加载,减小包体积
|
|
21
|
+
- 📖 **完整文档** - 详细的文档和示例
|
|
22
|
+
- 🎮 **流畅动画** - 精心设计的交互动画
|
|
23
|
+
|
|
24
|
+
## 📦 安装
|
|
25
|
+
|
|
26
|
+
### NPM 安装
|
|
6
27
|
|
|
7
28
|
```bash
|
|
8
|
-
npm install im-ui-mobile
|
|
29
|
+
npm install im-ui-mobile --save
|
|
30
|
+
# 或
|
|
31
|
+
yarn add im-ui-mobile
|
|
9
32
|
```
|
|
10
33
|
|
|
11
34
|
## 演示
|
|
12
35
|
|
|
13
|
-
|
|
36
|
+
微信扫码,查看演示
|
|
14
37
|
|
|
15
38
|

|
|
16
39
|
|
|
@@ -20,12 +20,6 @@
|
|
|
20
20
|
@chooseavatar="onChooseAvatar">
|
|
21
21
|
<!-- 加载状态 -->
|
|
22
22
|
<view v-if="loading && showLoading" class="im-button__loading">
|
|
23
|
-
<!-- <view class="im-button__loading-spinner" :style="loadingStyle">
|
|
24
|
-
<view v-for="n in loadingDotCount" :key="n" class="im-button__loading-dot" :style="{
|
|
25
|
-
animationDelay: `${(n - 1) * 0.15}s`,
|
|
26
|
-
backgroundColor: loadingColor
|
|
27
|
-
}" />
|
|
28
|
-
</view> -->
|
|
29
23
|
<text v-if="loadingText" class="im-button__loading-text">
|
|
30
24
|
{{ loadingText }}
|
|
31
25
|
</text>
|
|
@@ -63,7 +57,7 @@
|
|
|
63
57
|
</template>
|
|
64
58
|
|
|
65
59
|
<script setup lang="ts">
|
|
66
|
-
import { computed
|
|
60
|
+
import { computed } from 'vue'
|
|
67
61
|
import { validator } from '../../index'
|
|
68
62
|
|
|
69
63
|
// 定义类型
|
|
@@ -109,7 +103,7 @@ interface Props {
|
|
|
109
103
|
color?: string
|
|
110
104
|
bgColor?: string
|
|
111
105
|
borderColor?: string
|
|
112
|
-
border
|
|
106
|
+
border?: boolean
|
|
113
107
|
textColor?: string
|
|
114
108
|
textSize?: TextSize
|
|
115
109
|
width?: string
|
|
@@ -377,7 +371,7 @@ uni-button:after {
|
|
|
377
371
|
left: 0;
|
|
378
372
|
right: 0;
|
|
379
373
|
bottom: 0;
|
|
380
|
-
border: 1rpx solid currentColor
|
|
374
|
+
border: 1rpx solid currentColor; //
|
|
381
375
|
border-radius: inherit;
|
|
382
376
|
pointer-events: none;
|
|
383
377
|
transform: scale(0.5);
|
|
@@ -93,8 +93,7 @@
|
|
|
93
93
|
<script setup lang="ts">
|
|
94
94
|
import { computed } from 'vue'
|
|
95
95
|
import type { CSSProperties } from 'vue'
|
|
96
|
-
import
|
|
97
|
-
import ImBadge from '../im-badge/im-badge.vue'
|
|
96
|
+
import ImButton from '../im-button/im-button.vue'
|
|
98
97
|
|
|
99
98
|
// 类型定义
|
|
100
99
|
interface CardAction {
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
<script setup lang="ts">
|
|
99
99
|
import { ref, computed, watch, nextTick } from 'vue'
|
|
100
100
|
import type { Modal as ModalType } from 'im-ui-mobile'
|
|
101
|
+
import ImModal from '../im-modal/im-modal.vue'
|
|
101
102
|
|
|
102
103
|
// 定义对话框类型
|
|
103
104
|
type DialogType = 'alert' | 'confirm' | 'prompt' | 'success' | 'error' | 'warning' | 'info' | 'html' | 'custom'
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
<script setup lang="ts">
|
|
42
42
|
import { ref, computed } from 'vue'
|
|
43
|
-
import
|
|
44
|
-
import ImVirtualList from '../im-virtual-list/im-virtual-list.vue'
|
|
43
|
+
import ImPopup from '../im-popup/im-popup.vue'
|
|
45
44
|
|
|
46
45
|
interface Props {
|
|
47
46
|
group?: any;
|
|
@@ -36,9 +36,11 @@
|
|
|
36
36
|
|
|
37
37
|
<script setup lang="ts">
|
|
38
38
|
import { ref, computed } from 'vue'
|
|
39
|
+
import { GroupMember } from '../../libs';
|
|
40
|
+
import ImPopup from '../im-popup/im-popup.vue'
|
|
39
41
|
import ImAvatar from '../im-avatar/im-avatar.vue'
|
|
42
|
+
import ImSearch from '../im-search/im-search.vue'
|
|
40
43
|
import ImVirtualList from '../im-virtual-list/im-virtual-list.vue'
|
|
41
|
-
import { GroupMember } from '../../libs';
|
|
42
44
|
|
|
43
45
|
interface Props {
|
|
44
46
|
ownerId?: number;
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
<script setup lang="ts">
|
|
118
118
|
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
|
119
119
|
import { useDynamicRefs, datetime } from 'im-ui-mobile'
|
|
120
|
-
import { MESSAGE_TYPE, MESSAGE_STATUS } from '
|
|
120
|
+
import type { MESSAGE_TYPE, MESSAGE_STATUS } from 'im-ui-mobile'
|
|
121
121
|
import { Message, GroupMember } from '../../libs'
|
|
122
122
|
|
|
123
123
|
// 使用动态引用
|
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<im-popup
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
:
|
|
8
|
-
|
|
9
|
-
:overlay-color="overlayColor"
|
|
10
|
-
:overlay-opacity="overlayOpacity"
|
|
11
|
-
:close-on-click-overlay="closeOnClickOverlay"
|
|
12
|
-
:lock-scroll="lockScroll"
|
|
13
|
-
:width="computedWidth"
|
|
14
|
-
:border-radius="borderRadius"
|
|
15
|
-
:background-color="backgroundColor"
|
|
16
|
-
:z-index="zIndex"
|
|
17
|
-
:show-close="showClose && showHeader"
|
|
18
|
-
:close-position="closePosition"
|
|
19
|
-
:safe-area-inset-bottom="safeAreaInsetBottom"
|
|
20
|
-
:custom-class="customClass"
|
|
21
|
-
@open="handleOpen"
|
|
22
|
-
@opened="handleOpened"
|
|
23
|
-
@close="handleClose"
|
|
24
|
-
@closed="handleClosed"
|
|
25
|
-
@click-overlay="handleClickOverlay"
|
|
26
|
-
>
|
|
2
|
+
<im-popup v-model:show="visible" ref="popupRef" :position="position" :animation="animation" :duration="duration"
|
|
3
|
+
:show-overlay="showOverlay" :overlay-color="overlayColor" :overlay-opacity="overlayOpacity"
|
|
4
|
+
:close-on-click-overlay="closeOnClickOverlay" :lock-scroll="lockScroll" :width="computedWidth"
|
|
5
|
+
:border-radius="borderRadius" :background-color="backgroundColor" :z-index="zIndex"
|
|
6
|
+
:show-close="showClose && showHeader" :close-position="closePosition" :safe-area-inset-bottom="safeAreaInsetBottom"
|
|
7
|
+
:custom-class="customClass" @open="handleOpen" @opened="handleOpened" @close="handleClose" @closed="handleClosed"
|
|
8
|
+
@click-overlay="handleClickOverlay">
|
|
27
9
|
<!-- 头部 -->
|
|
28
10
|
<view v-if="showHeader" class="im-modal__header">
|
|
29
11
|
<slot name="header">
|
|
@@ -48,30 +30,14 @@
|
|
|
48
30
|
<slot name="footer">
|
|
49
31
|
<view class="im-modal__footer-buttons">
|
|
50
32
|
<!-- 取消按钮 -->
|
|
51
|
-
<im-button
|
|
52
|
-
|
|
53
|
-
class="im-modal__button"
|
|
54
|
-
:type="cancelType"
|
|
55
|
-
:size="buttonSize"
|
|
56
|
-
:plain="cancelPlain"
|
|
57
|
-
:disabled="cancelDisabled"
|
|
58
|
-
:loading="cancelLoading"
|
|
59
|
-
@click="handleCancel"
|
|
60
|
-
>
|
|
33
|
+
<im-button v-if="showCancel" class="im-modal__button" :type="cancelType" :size="buttonSize"
|
|
34
|
+
:plain="cancelPlain" :disabled="cancelDisabled" :loading="cancelLoading" @click="handleCancel">
|
|
61
35
|
{{ cancelText }}
|
|
62
36
|
</im-button>
|
|
63
37
|
|
|
64
38
|
<!-- 确认按钮 -->
|
|
65
|
-
<im-button
|
|
66
|
-
|
|
67
|
-
class="im-modal__button"
|
|
68
|
-
:type="confirmType"
|
|
69
|
-
:size="buttonSize"
|
|
70
|
-
:plain="confirmPlain"
|
|
71
|
-
:disabled="confirmDisabled"
|
|
72
|
-
:loading="confirmLoading"
|
|
73
|
-
@click="handleConfirm"
|
|
74
|
-
>
|
|
39
|
+
<im-button v-if="showConfirm" class="im-modal__button" :type="confirmType" :size="buttonSize"
|
|
40
|
+
:plain="confirmPlain" :disabled="confirmDisabled" :loading="confirmLoading" @click="handleConfirm">
|
|
75
41
|
{{ confirmText }}
|
|
76
42
|
</im-button>
|
|
77
43
|
</view>
|
|
@@ -83,25 +49,27 @@
|
|
|
83
49
|
<script setup lang="ts">
|
|
84
50
|
import { ref, computed } from 'vue'
|
|
85
51
|
import type { Popup as PopupType } from 'im-ui-mobile'
|
|
52
|
+
import ImPopup from '../im-popup/im-popup.vue'
|
|
53
|
+
import ImButton from '../im-button/im-button.vue'
|
|
86
54
|
|
|
87
55
|
// 定义 Props
|
|
88
56
|
interface Props {
|
|
89
57
|
// 显示控制
|
|
90
58
|
show?: boolean
|
|
91
59
|
modelValue?: boolean
|
|
92
|
-
|
|
60
|
+
|
|
93
61
|
// 标题和内容
|
|
94
62
|
title?: string
|
|
95
63
|
subtitle?: string
|
|
96
64
|
content?: string
|
|
97
|
-
|
|
65
|
+
|
|
98
66
|
// 头部配置
|
|
99
67
|
showHeader?: boolean
|
|
100
68
|
showSubtitle?: boolean
|
|
101
|
-
|
|
69
|
+
|
|
102
70
|
// 内容配置
|
|
103
71
|
bodyStyle?: Record<string, string>
|
|
104
|
-
|
|
72
|
+
|
|
105
73
|
// 底部按钮配置
|
|
106
74
|
showFooter?: boolean
|
|
107
75
|
showCancel?: boolean
|
|
@@ -117,7 +85,7 @@ interface Props {
|
|
|
117
85
|
cancelLoading?: boolean
|
|
118
86
|
confirmLoading?: boolean
|
|
119
87
|
buttonSize?: 'small' | 'medium' | 'large'
|
|
120
|
-
|
|
88
|
+
|
|
121
89
|
// 弹窗配置(传递给 im-popup)
|
|
122
90
|
position?: 'center' | 'top' | 'bottom' | 'left' | 'right'
|
|
123
91
|
animation?: 'fade' | 'slide' | 'zoom' | 'none'
|
|
@@ -137,7 +105,7 @@ interface Props {
|
|
|
137
105
|
closePosition?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
|
138
106
|
safeAreaInsetBottom?: boolean
|
|
139
107
|
customClass?: string
|
|
140
|
-
|
|
108
|
+
|
|
141
109
|
// 行为控制
|
|
142
110
|
closeOnClickAction?: boolean
|
|
143
111
|
showCloseAfterConfirm?: boolean
|
|
@@ -165,16 +133,16 @@ interface Emits {
|
|
|
165
133
|
const props = withDefaults(defineProps<Props>(), {
|
|
166
134
|
show: false,
|
|
167
135
|
modelValue: false,
|
|
168
|
-
|
|
136
|
+
|
|
169
137
|
title: '提示',
|
|
170
138
|
subtitle: '',
|
|
171
139
|
content: '',
|
|
172
|
-
|
|
140
|
+
|
|
173
141
|
showHeader: true,
|
|
174
142
|
showSubtitle: true,
|
|
175
|
-
|
|
143
|
+
|
|
176
144
|
bodyStyle: () => ({}),
|
|
177
|
-
|
|
145
|
+
|
|
178
146
|
showFooter: true,
|
|
179
147
|
showCancel: true,
|
|
180
148
|
showConfirm: true,
|
|
@@ -189,7 +157,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
189
157
|
cancelLoading: false,
|
|
190
158
|
confirmLoading: false,
|
|
191
159
|
buttonSize: 'medium',
|
|
192
|
-
|
|
160
|
+
|
|
193
161
|
position: 'center',
|
|
194
162
|
animation: 'zoom',
|
|
195
163
|
duration: 300,
|
|
@@ -208,7 +176,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
208
176
|
closePosition: 'top-right',
|
|
209
177
|
safeAreaInsetBottom: true,
|
|
210
178
|
customClass: '',
|
|
211
|
-
|
|
179
|
+
|
|
212
180
|
closeOnClickAction: true,
|
|
213
181
|
showCloseAfterConfirm: true,
|
|
214
182
|
showCloseAfterCancel: true
|
|
@@ -231,17 +199,17 @@ const visible = computed({
|
|
|
231
199
|
// 计算宽度
|
|
232
200
|
const computedWidth = computed(() => {
|
|
233
201
|
let width = props.width
|
|
234
|
-
|
|
202
|
+
|
|
235
203
|
// 添加最大宽度限制
|
|
236
204
|
if (props.maxWidth) {
|
|
237
205
|
width = `min(${props.width}, ${props.maxWidth})`
|
|
238
206
|
}
|
|
239
|
-
|
|
207
|
+
|
|
240
208
|
// 添加最小宽度限制
|
|
241
209
|
if (props.minWidth) {
|
|
242
210
|
width = `max(${width}, ${props.minWidth})`
|
|
243
211
|
}
|
|
244
|
-
|
|
212
|
+
|
|
245
213
|
return width
|
|
246
214
|
})
|
|
247
215
|
|
|
@@ -292,7 +260,7 @@ const handleCancel = () => {
|
|
|
292
260
|
|
|
293
261
|
// 打开模态框
|
|
294
262
|
const open = () => {
|
|
295
|
-
|
|
263
|
+
popupRef.value.open()
|
|
296
264
|
}
|
|
297
265
|
|
|
298
266
|
// 关闭模态框
|
|
@@ -318,12 +286,12 @@ defineExpose({
|
|
|
318
286
|
&__header {
|
|
319
287
|
padding: 40rpx 40rpx 20rpx;
|
|
320
288
|
border-bottom: 2rpx solid #f5f5f5;
|
|
321
|
-
|
|
289
|
+
|
|
322
290
|
&-content {
|
|
323
291
|
text-align: center;
|
|
324
292
|
}
|
|
325
293
|
}
|
|
326
|
-
|
|
294
|
+
|
|
327
295
|
&__title {
|
|
328
296
|
display: block;
|
|
329
297
|
font-size: 36rpx;
|
|
@@ -331,17 +299,17 @@ defineExpose({
|
|
|
331
299
|
color: #333;
|
|
332
300
|
line-height: 1.4;
|
|
333
301
|
}
|
|
334
|
-
|
|
302
|
+
|
|
335
303
|
&__subtitle {
|
|
336
304
|
margin-top: 12rpx;
|
|
337
|
-
|
|
305
|
+
|
|
338
306
|
text {
|
|
339
307
|
font-size: 28rpx;
|
|
340
308
|
color: #999;
|
|
341
309
|
line-height: 1.4;
|
|
342
310
|
}
|
|
343
311
|
}
|
|
344
|
-
|
|
312
|
+
|
|
345
313
|
&__body {
|
|
346
314
|
padding: 40rpx;
|
|
347
315
|
font-size: 30rpx;
|
|
@@ -350,22 +318,22 @@ defineExpose({
|
|
|
350
318
|
overflow-y: auto;
|
|
351
319
|
max-height: 60vh;
|
|
352
320
|
}
|
|
353
|
-
|
|
321
|
+
|
|
354
322
|
&__content {
|
|
355
323
|
display: block;
|
|
356
324
|
text-align: center;
|
|
357
325
|
}
|
|
358
|
-
|
|
326
|
+
|
|
359
327
|
&__footer {
|
|
360
328
|
padding: 20rpx 40rpx 40rpx;
|
|
361
|
-
|
|
329
|
+
|
|
362
330
|
&-buttons {
|
|
363
331
|
display: flex;
|
|
364
332
|
gap: 20rpx;
|
|
365
333
|
justify-content: center;
|
|
366
334
|
}
|
|
367
335
|
}
|
|
368
|
-
|
|
336
|
+
|
|
369
337
|
&__button {
|
|
370
338
|
flex: 1;
|
|
371
339
|
}
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
100
100
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
|
101
101
|
import type { IconType } from '../../types/components/icon'
|
|
102
|
+
import ImIcon from '../im-icon/im-icon.vue'
|
|
102
103
|
|
|
103
104
|
// 类型定义
|
|
104
105
|
interface NavBarAction {
|
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
|
|
35
35
|
<script setup lang="ts">
|
|
36
36
|
import { ref } from 'vue'
|
|
37
|
+
import { Chat, Message } from '../../libs';
|
|
37
38
|
import ImAvatar from '../im-avatar/im-avatar.vue'
|
|
38
39
|
import ImVirtualList from '../im-virtual-list/im-virtual-list.vue'
|
|
39
|
-
import
|
|
40
|
+
import ImTabs from '../im-tabs/im-tabs.vue'
|
|
41
|
+
import ImPopup from '../im-popup/im-popup.vue'
|
|
40
42
|
|
|
41
43
|
interface Props {
|
|
42
44
|
msgInfo: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<view class="im-search" :class="[
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
isFocused ? 'im-search--focus' : '',
|
|
4
|
+
disabled ? 'im-search--disabled' : '',
|
|
5
|
+
`im-search--${size}`
|
|
6
|
+
]">
|
|
7
7
|
<!-- 搜索图标 -->
|
|
8
8
|
<view class="im-search__icon" @tap="handleIconClick">
|
|
9
9
|
<slot name="icon">
|
|
@@ -11,52 +11,29 @@
|
|
|
11
11
|
<im-icon size="38" name="search" />
|
|
12
12
|
</slot>
|
|
13
13
|
</view>
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
<!-- 搜索输入框 -->
|
|
16
|
-
<input
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
:placeholder-style="placeholderStyle"
|
|
23
|
-
:maxlength="maxlength"
|
|
24
|
-
:disabled="disabled"
|
|
25
|
-
:focus="autoFocus"
|
|
26
|
-
:confirm-type="confirmType"
|
|
27
|
-
:adjust-position="adjustPosition"
|
|
28
|
-
:cursor-spacing="cursorSpacing"
|
|
29
|
-
:show-confirm-bar="showConfirmBar"
|
|
30
|
-
@input="handleInput"
|
|
31
|
-
@focus="handleFocus"
|
|
32
|
-
@blur="handleBlur"
|
|
33
|
-
@confirm="handleConfirm"
|
|
34
|
-
@keyboardheightchange="handleKeyboardHeightChange"
|
|
35
|
-
/>
|
|
36
|
-
|
|
16
|
+
<input ref="inputRef" class="im-search__input" :type="inputType" :value="modelValue" :placeholder="placeholder"
|
|
17
|
+
:placeholder-style="placeholderStyle" :maxlength="maxlength" :disabled="disabled" :focus="autoFocus"
|
|
18
|
+
:confirm-type="confirmType" :adjust-position="adjustPosition" :cursor-spacing="cursorSpacing"
|
|
19
|
+
:show-confirm-bar="showConfirmBar" @input="handleInput" @focus="handleFocus" @blur="handleBlur"
|
|
20
|
+
@confirm="handleConfirm" @keyboardheightchange="handleKeyboardHeightChange" />
|
|
21
|
+
|
|
37
22
|
<!-- 清除按钮 -->
|
|
38
|
-
<view
|
|
39
|
-
v-if="showClear && modelValue && !disabled"
|
|
40
|
-
class="im-search__clear"
|
|
41
|
-
@tap="handleClear"
|
|
42
|
-
>
|
|
23
|
+
<view v-if="showClear && modelValue && !disabled" class="im-search__clear" @tap="handleClear">
|
|
43
24
|
<slot name="clear-icon">
|
|
44
25
|
<text class="im-search__clear-text">✕</text>
|
|
45
26
|
</slot>
|
|
46
27
|
</view>
|
|
47
|
-
|
|
28
|
+
|
|
48
29
|
<!-- 取消按钮 -->
|
|
49
|
-
<view
|
|
50
|
-
|
|
51
|
-
class="im-search__cancel"
|
|
52
|
-
:class="{ 'im-search__cancel--show': modelValue || isFocused || alwaysShowCancel }"
|
|
53
|
-
@tap="handleCancel"
|
|
54
|
-
>
|
|
30
|
+
<view v-if="showCancel" class="im-search__cancel"
|
|
31
|
+
:class="{ 'im-search__cancel--show': modelValue || isFocused || alwaysShowCancel }" @tap="handleCancel">
|
|
55
32
|
<slot name="cancel-text">
|
|
56
33
|
<text class="im-search__cancel-text">{{ cancelText }}</text>
|
|
57
34
|
</slot>
|
|
58
35
|
</view>
|
|
59
|
-
|
|
36
|
+
|
|
60
37
|
<!-- 搜索历史 -->
|
|
61
38
|
<view v-if="showHistory && historyVisible && searchHistory.length > 0" class="im-search__history">
|
|
62
39
|
<view class="im-search__history-header">
|
|
@@ -66,48 +43,38 @@
|
|
|
66
43
|
</view>
|
|
67
44
|
</view>
|
|
68
45
|
<view class="im-search__history-list">
|
|
69
|
-
<view
|
|
70
|
-
|
|
71
|
-
:key="index"
|
|
72
|
-
class="im-search__history-item"
|
|
73
|
-
@tap="handleHistoryClick(item)"
|
|
74
|
-
>
|
|
46
|
+
<view v-for="(item, index) in searchHistory" :key="index" class="im-search__history-item"
|
|
47
|
+
@tap="handleHistoryClick(item)">
|
|
75
48
|
<text class="im-search__history-item-text">{{ item }}</text>
|
|
76
|
-
<text
|
|
77
|
-
class="im-search__history-item-delete"
|
|
78
|
-
@tap.stop="deleteHistoryItem(index)"
|
|
79
|
-
>✕</text>
|
|
49
|
+
<text class="im-search__history-item-delete" @tap.stop="deleteHistoryItem(index)">✕</text>
|
|
80
50
|
</view>
|
|
81
51
|
</view>
|
|
82
52
|
</view>
|
|
83
|
-
|
|
53
|
+
|
|
84
54
|
<!-- 搜索建议 -->
|
|
85
55
|
<view v-if="showSuggestions && suggestionsVisible && suggestions.length > 0" class="im-search__suggestions">
|
|
86
|
-
<view
|
|
87
|
-
|
|
88
|
-
:key="index"
|
|
89
|
-
class="im-search__suggestion-item"
|
|
90
|
-
@tap="handleSuggestionClick(item)"
|
|
91
|
-
>
|
|
56
|
+
<view v-for="(item, index) in suggestions" :key="index" class="im-search__suggestion-item"
|
|
57
|
+
@tap="handleSuggestionClick(item)">
|
|
92
58
|
<slot name="suggestion" :item="item" :index="index">
|
|
93
59
|
<text class="im-search__suggestion-text">{{ formatSuggestion(item) }}</text>
|
|
94
60
|
</slot>
|
|
95
61
|
</view>
|
|
96
62
|
</view>
|
|
97
|
-
|
|
63
|
+
|
|
98
64
|
<!-- 搜索结果 -->
|
|
99
65
|
<slot name="results" :results="searchResults"></slot>
|
|
100
66
|
</view>
|
|
101
67
|
</template>
|
|
102
68
|
|
|
103
69
|
<script setup lang="ts">
|
|
104
|
-
import { ref,
|
|
70
|
+
import { ref, nextTick } from 'vue'
|
|
71
|
+
import ImIcon from '../im-icon/im-icon.vue'
|
|
105
72
|
|
|
106
73
|
// 定义 Props
|
|
107
74
|
interface Props {
|
|
108
75
|
// 值
|
|
109
76
|
modelValue?: string
|
|
110
|
-
|
|
77
|
+
|
|
111
78
|
// 样式配置
|
|
112
79
|
shape?: 'square' | 'round'
|
|
113
80
|
size?: 'small' | 'medium' | 'large'
|
|
@@ -115,7 +82,7 @@ interface Props {
|
|
|
115
82
|
borderColor?: string
|
|
116
83
|
textColor?: string
|
|
117
84
|
placeholderColor?: string
|
|
118
|
-
|
|
85
|
+
|
|
119
86
|
// 输入框配置
|
|
120
87
|
placeholder?: string
|
|
121
88
|
placeholderStyle?: string
|
|
@@ -127,7 +94,7 @@ interface Props {
|
|
|
127
94
|
adjustPosition?: boolean
|
|
128
95
|
cursorSpacing?: number
|
|
129
96
|
showConfirmBar?: boolean
|
|
130
|
-
|
|
97
|
+
|
|
131
98
|
// 功能配置
|
|
132
99
|
showClear?: boolean
|
|
133
100
|
showCancel?: boolean
|
|
@@ -135,14 +102,14 @@ interface Props {
|
|
|
135
102
|
cancelText?: string
|
|
136
103
|
showHistory?: boolean
|
|
137
104
|
showSuggestions?: boolean
|
|
138
|
-
|
|
105
|
+
|
|
139
106
|
// 搜索相关
|
|
140
107
|
searchDelay?: number
|
|
141
108
|
minLength?: number
|
|
142
109
|
maxHistory?: number
|
|
143
110
|
suggestions?: any[]
|
|
144
111
|
suggestionFormatter?: (item: any) => string
|
|
145
|
-
|
|
112
|
+
|
|
146
113
|
// 自定义事件
|
|
147
114
|
onSearch?: (value: string) => void
|
|
148
115
|
onClear?: () => void
|
|
@@ -167,14 +134,14 @@ interface Emits {
|
|
|
167
134
|
// 定义 Props 默认值
|
|
168
135
|
const props = withDefaults(defineProps<Props>(), {
|
|
169
136
|
modelValue: '',
|
|
170
|
-
|
|
137
|
+
|
|
171
138
|
shape: 'round',
|
|
172
139
|
size: 'medium',
|
|
173
140
|
bgColor: '#f5f5f5',
|
|
174
141
|
borderColor: '#e0e0e0',
|
|
175
142
|
textColor: '#333333',
|
|
176
143
|
placeholderColor: '#999999',
|
|
177
|
-
|
|
144
|
+
|
|
178
145
|
placeholder: '请输入搜索内容',
|
|
179
146
|
placeholderStyle: '',
|
|
180
147
|
inputType: 'text',
|
|
@@ -185,19 +152,19 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
185
152
|
adjustPosition: true,
|
|
186
153
|
cursorSpacing: 0,
|
|
187
154
|
showConfirmBar: true,
|
|
188
|
-
|
|
155
|
+
|
|
189
156
|
showClear: true,
|
|
190
157
|
showCancel: true,
|
|
191
158
|
alwaysShowCancel: false,
|
|
192
159
|
cancelText: '取消',
|
|
193
160
|
showHistory: true,
|
|
194
161
|
showSuggestions: false,
|
|
195
|
-
|
|
162
|
+
|
|
196
163
|
searchDelay: 300,
|
|
197
164
|
minLength: 1,
|
|
198
165
|
maxHistory: 10,
|
|
199
166
|
suggestions: () => [],
|
|
200
|
-
suggestionFormatter: (item:any) => typeof item === 'string' ? item : item.text || item.name || JSON.stringify(item)
|
|
167
|
+
suggestionFormatter: (item: any) => typeof item === 'string' ? item : item.text || item.name || JSON.stringify(item)
|
|
201
168
|
})
|
|
202
169
|
|
|
203
170
|
const emit = defineEmits<Emits>()
|
|
@@ -228,21 +195,21 @@ const initHistory = () => {
|
|
|
228
195
|
// 保存搜索历史
|
|
229
196
|
const saveHistory = (value: string) => {
|
|
230
197
|
if (!value.trim()) return
|
|
231
|
-
|
|
198
|
+
|
|
232
199
|
// 移除重复项
|
|
233
200
|
const index = searchHistory.value.indexOf(value)
|
|
234
201
|
if (index > -1) {
|
|
235
202
|
searchHistory.value.splice(index, 1)
|
|
236
203
|
}
|
|
237
|
-
|
|
204
|
+
|
|
238
205
|
// 添加到开头
|
|
239
206
|
searchHistory.value.unshift(value)
|
|
240
|
-
|
|
207
|
+
|
|
241
208
|
// 限制历史记录数量
|
|
242
209
|
if (searchHistory.value.length > props.maxHistory) {
|
|
243
210
|
searchHistory.value = searchHistory.value.slice(0, props.maxHistory)
|
|
244
211
|
}
|
|
245
|
-
|
|
212
|
+
|
|
246
213
|
// 保存到本地存储
|
|
247
214
|
try {
|
|
248
215
|
uni.setStorageSync('im_search_history', JSON.stringify(searchHistory.value))
|
|
@@ -276,12 +243,12 @@ const handleInput = (event: any) => {
|
|
|
276
243
|
const value = event.detail?.value || ''
|
|
277
244
|
emit('update:modelValue', value)
|
|
278
245
|
emit('input', value)
|
|
279
|
-
|
|
246
|
+
|
|
280
247
|
// 防抖搜索
|
|
281
248
|
if (searchTimer.value) {
|
|
282
249
|
clearTimeout(searchTimer.value)
|
|
283
250
|
}
|
|
284
|
-
|
|
251
|
+
|
|
285
252
|
searchTimer.value = setTimeout(() => {
|
|
286
253
|
if (value.trim().length >= props.minLength) {
|
|
287
254
|
emit('search', value)
|
|
@@ -289,7 +256,7 @@ const handleInput = (event: any) => {
|
|
|
289
256
|
props.onSearch(value)
|
|
290
257
|
}
|
|
291
258
|
}
|
|
292
|
-
|
|
259
|
+
|
|
293
260
|
// 显示/隐藏建议
|
|
294
261
|
suggestionsVisible.value = value.trim().length > 0 && props.showSuggestions
|
|
295
262
|
historyVisible.value = value.trim().length === 0 && props.showHistory
|
|
@@ -328,7 +295,7 @@ const handleClear = () => {
|
|
|
328
295
|
emit('clear')
|
|
329
296
|
historyVisible.value = props.showHistory
|
|
330
297
|
suggestionsVisible.value = false
|
|
331
|
-
|
|
298
|
+
|
|
332
299
|
// 聚焦输入框
|
|
333
300
|
nextTick(() => {
|
|
334
301
|
if (inputRef.value) {
|
|
@@ -345,12 +312,12 @@ const handleCancel = () => {
|
|
|
345
312
|
historyVisible.value = false
|
|
346
313
|
suggestionsVisible.value = false
|
|
347
314
|
isFocused.value = false
|
|
348
|
-
|
|
315
|
+
|
|
349
316
|
// 失去焦点
|
|
350
317
|
if (inputRef.value) {
|
|
351
318
|
blur()
|
|
352
319
|
}
|
|
353
|
-
|
|
320
|
+
|
|
354
321
|
if (props.onCancel) {
|
|
355
322
|
props.onCancel()
|
|
356
323
|
}
|
|
@@ -370,7 +337,7 @@ const handleHistoryClick = (value: string) => {
|
|
|
370
337
|
emit('history-click', value)
|
|
371
338
|
saveHistory(value)
|
|
372
339
|
historyVisible.value = false
|
|
373
|
-
|
|
340
|
+
|
|
374
341
|
// 触发搜索
|
|
375
342
|
if (value.trim().length >= props.minLength) {
|
|
376
343
|
emit('search', value)
|
|
@@ -378,7 +345,7 @@ const handleHistoryClick = (value: string) => {
|
|
|
378
345
|
props.onSearch(value)
|
|
379
346
|
}
|
|
380
347
|
}
|
|
381
|
-
|
|
348
|
+
|
|
382
349
|
// 聚焦输入框
|
|
383
350
|
nextTick(() => {
|
|
384
351
|
if (inputRef.value) {
|
|
@@ -395,7 +362,7 @@ const handleSuggestionClick = (item: any) => {
|
|
|
395
362
|
emit('suggestion-click', item)
|
|
396
363
|
saveHistory(value)
|
|
397
364
|
suggestionsVisible.value = false
|
|
398
|
-
|
|
365
|
+
|
|
399
366
|
// 触发搜索
|
|
400
367
|
if (value.trim().length >= props.minLength) {
|
|
401
368
|
emit('search', value)
|
|
@@ -424,7 +391,7 @@ const setResults = (results: any[]) => {
|
|
|
424
391
|
const focus = () => {
|
|
425
392
|
if (!props.disabled) {
|
|
426
393
|
isFocused.value = true
|
|
427
|
-
|
|
394
|
+
|
|
428
395
|
nextTick(() => {
|
|
429
396
|
// 使用 uniapp 的选择器 API
|
|
430
397
|
uni.createSelectorQuery()
|
|
@@ -439,7 +406,7 @@ const focus = () => {
|
|
|
439
406
|
})
|
|
440
407
|
}
|
|
441
408
|
})
|
|
442
|
-
|
|
409
|
+
|
|
443
410
|
// 直接设置焦点(需要确保组件已渲染)
|
|
444
411
|
setTimeout(() => {
|
|
445
412
|
if (inputRef.value) {
|
|
@@ -494,23 +461,23 @@ defineExpose({
|
|
|
494
461
|
border-radius: 8rpx;
|
|
495
462
|
transition: all 0.3s ease;
|
|
496
463
|
position: relative;
|
|
497
|
-
|
|
464
|
+
|
|
498
465
|
&--focus {
|
|
499
466
|
border-color: #409eff;
|
|
500
467
|
background-color: #ffffff;
|
|
501
468
|
box-shadow: 0 4rpx 12rpx rgba(64, 158, 255, 0.1);
|
|
502
469
|
}
|
|
503
|
-
|
|
470
|
+
|
|
504
471
|
&--disabled {
|
|
505
472
|
opacity: 0.6;
|
|
506
473
|
cursor: not-allowed;
|
|
507
474
|
}
|
|
508
|
-
|
|
475
|
+
|
|
509
476
|
// 圆角样式
|
|
510
477
|
&--round {
|
|
511
478
|
border-radius: 100rpx;
|
|
512
479
|
}
|
|
513
|
-
|
|
480
|
+
|
|
514
481
|
// 尺寸样式
|
|
515
482
|
&--small {
|
|
516
483
|
padding: 12rpx 20rpx;
|
|
@@ -519,32 +486,37 @@ defineExpose({
|
|
|
519
486
|
font-size: 26rpx;
|
|
520
487
|
height: 40rpx;
|
|
521
488
|
}
|
|
489
|
+
|
|
522
490
|
.im-search__icon {
|
|
523
491
|
width: 36rpx;
|
|
524
492
|
height: 36rpx;
|
|
525
493
|
margin-right: 12rpx;
|
|
526
494
|
}
|
|
527
495
|
}
|
|
528
|
-
|
|
496
|
+
|
|
529
497
|
&--medium {
|
|
530
498
|
padding: 16rpx 24rpx;
|
|
499
|
+
|
|
531
500
|
.im-search__input {
|
|
532
501
|
font-size: 28rpx;
|
|
533
502
|
height: 44rpx;
|
|
534
503
|
}
|
|
504
|
+
|
|
535
505
|
.im-search__icon {
|
|
536
506
|
width: 40rpx;
|
|
537
507
|
height: 40rpx;
|
|
538
508
|
margin-right: 16rpx;
|
|
539
509
|
}
|
|
540
510
|
}
|
|
541
|
-
|
|
511
|
+
|
|
542
512
|
&--large {
|
|
543
513
|
padding: 20rpx 28rpx;
|
|
514
|
+
|
|
544
515
|
.im-search__input {
|
|
545
516
|
font-size: 32rpx;
|
|
546
517
|
height: 48rpx;
|
|
547
518
|
}
|
|
519
|
+
|
|
548
520
|
.im-search__icon {
|
|
549
521
|
width: 44rpx;
|
|
550
522
|
height: 44rpx;
|
|
@@ -559,7 +531,7 @@ defineExpose({
|
|
|
559
531
|
align-items: center;
|
|
560
532
|
justify-content: center;
|
|
561
533
|
cursor: pointer;
|
|
562
|
-
|
|
534
|
+
|
|
563
535
|
&-text {
|
|
564
536
|
font-size: 32rpx;
|
|
565
537
|
color: v-bind('props.placeholderColor');
|
|
@@ -574,11 +546,11 @@ defineExpose({
|
|
|
574
546
|
background-color: transparent;
|
|
575
547
|
border: none;
|
|
576
548
|
outline: none;
|
|
577
|
-
|
|
549
|
+
|
|
578
550
|
&::placeholder {
|
|
579
551
|
color: v-bind('props.placeholderColor');
|
|
580
552
|
}
|
|
581
|
-
|
|
553
|
+
|
|
582
554
|
&:disabled {
|
|
583
555
|
cursor: not-allowed;
|
|
584
556
|
}
|
|
@@ -596,12 +568,12 @@ defineExpose({
|
|
|
596
568
|
border-radius: 50%;
|
|
597
569
|
cursor: pointer;
|
|
598
570
|
transition: all 0.3s;
|
|
599
|
-
|
|
571
|
+
|
|
600
572
|
&:active {
|
|
601
573
|
background-color: #c0c0c0;
|
|
602
574
|
transform: scale(0.95);
|
|
603
575
|
}
|
|
604
|
-
|
|
576
|
+
|
|
605
577
|
&-text {
|
|
606
578
|
font-size: 20rpx;
|
|
607
579
|
color: #666666;
|
|
@@ -616,19 +588,19 @@ defineExpose({
|
|
|
616
588
|
transform: translateX(20rpx);
|
|
617
589
|
transition: all 0.3s ease;
|
|
618
590
|
pointer-events: none;
|
|
619
|
-
|
|
591
|
+
|
|
620
592
|
&--show {
|
|
621
593
|
opacity: 1;
|
|
622
594
|
transform: translateX(0);
|
|
623
595
|
pointer-events: auto;
|
|
624
596
|
}
|
|
625
|
-
|
|
597
|
+
|
|
626
598
|
&-text {
|
|
627
599
|
font-size: 28rpx;
|
|
628
600
|
color: #409eff;
|
|
629
601
|
cursor: pointer;
|
|
630
602
|
white-space: nowrap;
|
|
631
|
-
|
|
603
|
+
|
|
632
604
|
&:active {
|
|
633
605
|
opacity: 0.8;
|
|
634
606
|
}
|
|
@@ -665,11 +637,11 @@ defineExpose({
|
|
|
665
637
|
padding: 8rpx 16rpx;
|
|
666
638
|
border-radius: 6rpx;
|
|
667
639
|
cursor: pointer;
|
|
668
|
-
|
|
640
|
+
|
|
669
641
|
&:active {
|
|
670
642
|
background-color: #f5f5f5;
|
|
671
643
|
}
|
|
672
|
-
|
|
644
|
+
|
|
673
645
|
&-text {
|
|
674
646
|
font-size: 24rpx;
|
|
675
647
|
color: #666666;
|
|
@@ -689,11 +661,11 @@ defineExpose({
|
|
|
689
661
|
border-bottom: 2rpx solid #f5f5f5;
|
|
690
662
|
cursor: pointer;
|
|
691
663
|
transition: background-color 0.2s;
|
|
692
|
-
|
|
664
|
+
|
|
693
665
|
&:last-child {
|
|
694
666
|
border-bottom: none;
|
|
695
667
|
}
|
|
696
|
-
|
|
668
|
+
|
|
697
669
|
&:active {
|
|
698
670
|
background-color: #f9f9f9;
|
|
699
671
|
}
|
|
@@ -716,11 +688,11 @@ defineExpose({
|
|
|
716
688
|
color: #999999;
|
|
717
689
|
opacity: 0;
|
|
718
690
|
transition: opacity 0.3s;
|
|
719
|
-
|
|
691
|
+
|
|
720
692
|
.im-search__history-item:hover & {
|
|
721
693
|
opacity: 1;
|
|
722
694
|
}
|
|
723
|
-
|
|
695
|
+
|
|
724
696
|
&:active {
|
|
725
697
|
color: #ff4d4f;
|
|
726
698
|
}
|
|
@@ -745,11 +717,11 @@ defineExpose({
|
|
|
745
717
|
border-bottom: 2rpx solid #f5f5f5;
|
|
746
718
|
cursor: pointer;
|
|
747
719
|
transition: background-color 0.2s;
|
|
748
|
-
|
|
720
|
+
|
|
749
721
|
&:last-child {
|
|
750
722
|
border-bottom: none;
|
|
751
723
|
}
|
|
752
|
-
|
|
724
|
+
|
|
753
725
|
&:active {
|
|
754
726
|
background-color: #f9f9f9;
|
|
755
727
|
}
|
|
@@ -147,6 +147,8 @@ import type {
|
|
|
147
147
|
SkuEmits,
|
|
148
148
|
SelectedSku
|
|
149
149
|
} from '../../types/components/sku'
|
|
150
|
+
import ImPopup from '../im-popup/im-popup.vue'
|
|
151
|
+
import ImIcon from '../im-icon/im-icon.vue'
|
|
150
152
|
|
|
151
153
|
const props = withDefaults(defineProps<SkuProps>(), {
|
|
152
154
|
current: undefined,
|
package/package.json
CHANGED