1
+ <template >
2
+ <div class =" search-box" >
3
+ <div class =" search-input-wrapper" >
4
+ <div class =" input-container" >
5
+ <input
6
+ type =" text"
7
+ v-model =" searchValue"
8
+ placeholder =" 请输入地址"
9
+ class =" search-input"
10
+ @keyup.enter =" onSearch"
11
+ @input =" handleInput"
12
+ >
13
+ <!-- 搜索提示列表 -->
14
+ <div class =" search-tips" v-if =" tipList.length" >
15
+ <div
16
+ v-for =" (item, index) in tipList"
17
+ :key =" index"
18
+ class =" tip-item"
19
+ @click =" handleSelectTip(item)"
20
+ >
21
+ <div class =" name" v-html =" highlightKeyword(item.name)" ></div >
22
+ <div class =" district" >{{ item.district }}</div >
23
+ </div >
24
+ </div >
25
+ </div >
26
+ <button class =" search-btn" @click =" onSearch" >搜索</button >
27
+ </div >
28
+ </div >
29
+ </template >
30
+
31
+ <script setup>
32
+ import { ref , onMounted } from ' vue'
33
+
34
+ const props = defineProps ({
35
+ autoComplete: Object
36
+ })
37
+
38
+ const emit = defineEmits ([' search' , ' select' ])
39
+
40
+ const searchValue = ref (' ' )
41
+ const tipList = ref ([])
42
+
43
+ // 防抖函数
44
+ function debounce (fn , delay ) {
45
+ let timer = null
46
+ return function (... args ) {
47
+ if (timer) clearTimeout (timer)
48
+ timer = setTimeout (() => {
49
+ fn .apply (this , args)
50
+ }, delay)
51
+ }
52
+ }
53
+
54
+ // 处理输入
55
+ const handleInput = debounce (async () => {
56
+ if (! searchValue .value .trim ()) {
57
+ tipList .value = []
58
+ return
59
+ }
60
+
61
+ props .autoComplete .search (searchValue .value , (status , result ) => {
62
+ if (status === ' complete' && result .tips ) {
63
+ tipList .value = result .tips
64
+ } else {
65
+ tipList .value = []
66
+ }
67
+ })
68
+ }, 300 )
69
+
70
+ // 高亮关键字
71
+ const highlightKeyword = (text ) => {
72
+ if (! searchValue .value .trim ()) return text
73
+ const keyword = searchValue .value .trim ()
74
+ const reg = new RegExp (keyword, ' gi' )
75
+ return text .replace (reg, match => ` <span class="highlight">${ match} </span>` )
76
+ }
77
+
78
+ // 选择提示项
79
+ const handleSelectTip = (tip ) => {
80
+ searchValue .value = tip .name
81
+ tipList .value = []
82
+ emit (' select' , tip)
83
+ }
84
+
85
+ const onSearch = () => {
86
+ emit (' search' , searchValue .value )
87
+ }
88
+
89
+ // 点击其他地方关闭提示列表
90
+ onMounted (() => {
91
+ document .addEventListener (' click' , (e ) => {
92
+ const searchBox = document .querySelector (' .search-box' )
93
+ if (! searchBox? .contains (e .target )) {
94
+ tipList .value = []
95
+ }
96
+ })
97
+ })
98
+ < / script>
99
+
100
+ < style lang= " scss" scoped>
101
+ .search - box {
102
+ position: fixed;
103
+ top: 0 ;
104
+ left: 0 ;
105
+ right: 0 ;
106
+ z- index: 100 ;
107
+ background: #fff;
108
+ padding: 8px 12px ;
109
+ box- shadow: 0 2px 4px rgba (0 , 0 , 0 , 0.1 );
110
+
111
+ .search - input- wrapper {
112
+ display: flex;
113
+ align- items: center;
114
+ gap: 8px ;
115
+
116
+ .input - container {
117
+ flex: 1 ;
118
+ position: relative;
119
+
120
+ .search - input {
121
+ width: 100 % ;
122
+ height: 36px ;
123
+ padding: 0 12px ;
124
+ border: none;
125
+ background: #f7f8fa;
126
+ border- radius: 4px ;
127
+ font- size: 14px ;
128
+ outline: none;
129
+
130
+ & : focus {
131
+ & :: placeholder {
132
+ color: #c8c9cc;
133
+ }
134
+ }
135
+
136
+ & :: placeholder {
137
+ color: #969799 ;
138
+ }
139
+ }
140
+
141
+ .search - tips {
142
+ position: absolute;
143
+ top: 100 % ;
144
+ left: 0 ;
145
+ right: 0 ;
146
+ background: #fff;
147
+ border- radius: 4px ;
148
+ box- shadow: 0 2px 12px rgba (0 , 0 , 0 , 0.1 );
149
+ margin- top: 4px ;
150
+ max- height: 300px ;
151
+ overflow- y: auto;
152
+ z- index: 1000 ;
153
+
154
+ .tip - item {
155
+ padding: 10px 12px ;
156
+ cursor: pointer;
157
+
158
+ & : hover {
159
+ background: #f5f5f5;
160
+ }
161
+
162
+ .name {
163
+ font- size: 14px ;
164
+ color: #323233 ;
165
+ margin- bottom: 2px ;
166
+
167
+ : deep (.highlight ) {
168
+ color: #ee0a24;
169
+ }
170
+ }
171
+
172
+ .district {
173
+ font- size: 12px ;
174
+ color: #969799 ;
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ .search - btn {
181
+ padding: 0 16px ;
182
+ height: 36px ;
183
+ color: #1989fa ;
184
+ font- size: 14px ;
185
+ background: transparent;
186
+ border: none;
187
+ cursor: pointer;
188
+ white- space: nowrap;
189
+
190
+ & : active {
191
+ opacity: 0.8 ;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ < / style>
0 commit comments