Skip to content

Commit 159bc0e

Browse files
author
liuhan
committed
新增一些控件和关键字提示
1 parent 412efb5 commit 159bc0e

File tree

9 files changed

+1156
-107
lines changed

9 files changed

+1156
-107
lines changed

src/components/AMap/AddressList.vue

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<div class="address-list">
3+
<template v-if="!loading">
4+
<template v-if="addressList.length">
5+
<div
6+
v-for="item in addressList"
7+
:key="item.id"
8+
class="address-item"
9+
@click="handleSelect(item)"
10+
>
11+
<div class="title">{{ item.name }}</div>
12+
<div class="label">{{ item.address }}</div>
13+
</div>
14+
</template>
15+
<div v-else class="empty-status">
16+
<img src="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png" class="empty-image">
17+
<p class="empty-text">暂无地址数据</p>
18+
</div>
19+
</template>
20+
</div>
21+
</template>
22+
23+
<script setup>
24+
defineProps({
25+
loading: Boolean,
26+
addressList: {
27+
type: Array,
28+
default: () => []
29+
}
30+
})
31+
32+
const emit = defineEmits(['select'])
33+
34+
const handleSelect = (item) => {
35+
emit('select', item)
36+
}
37+
</script>
38+
39+
<style lang="scss" scoped>
40+
.address-list {
41+
height: calc(40vh - 54px);
42+
overflow-y: auto;
43+
background: #fff;
44+
45+
.empty-status {
46+
display: flex;
47+
flex-direction: column;
48+
align-items: center;
49+
justify-content: center;
50+
margin-top: 60px;
51+
52+
.empty-image {
53+
width: 60px;
54+
height: 60px;
55+
}
56+
57+
.empty-text {
58+
margin-top: 15px;
59+
font-size: 14px;
60+
color: #969799;
61+
}
62+
}
63+
64+
.address-item {
65+
padding: 16px;
66+
cursor: pointer;
67+
border-bottom: 1px solid #f5f5f5;
68+
transition: background-color 0.2s;
69+
70+
&:active {
71+
background-color: #f5f5f5;
72+
}
73+
74+
.title {
75+
font-size: 14px;
76+
color: #323233;
77+
margin-bottom: 4px;
78+
}
79+
80+
.label {
81+
font-size: 12px;
82+
color: #969799;
83+
}
84+
}
85+
}
86+
</style>

src/components/AMap/SearchBox.vue

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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

Comments
 (0)