refactor(components): 重构 SearchBar 组件并更新搜索页面
- 将 SearchBar 组件从原生 input 重构为 NutUI Input 实现,简化样式和逻辑 - 更新 components.d.ts 类型声明,移除未使用的 NutConfigProvider,修正 PlanPopup 导入路径 - 重构搜索页面,使用 NutTabs 实现分类切换,支持长列表测试数据 - 为搜索结果项添加动画效果,优化用户体验
Showing
3 changed files
with
58 additions
and
87 deletions
| ... | @@ -16,7 +16,6 @@ declare module 'vue' { | ... | @@ -16,7 +16,6 @@ declare module 'vue' { |
| 16 | NavHeader: typeof import('./src/components/NavHeader.vue')['default'] | 16 | NavHeader: typeof import('./src/components/NavHeader.vue')['default'] |
| 17 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] | 17 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] |
| 18 | NutButton: typeof import('@nutui/nutui-taro')['Button'] | 18 | NutButton: typeof import('@nutui/nutui-taro')['Button'] |
| 19 | - NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] | ||
| 20 | NutInput: typeof import('@nutui/nutui-taro')['Input'] | 19 | NutInput: typeof import('@nutui/nutui-taro')['Input'] |
| 21 | NutPicker: typeof import('@nutui/nutui-taro')['Picker'] | 20 | NutPicker: typeof import('@nutui/nutui-taro')['Picker'] |
| 22 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] | 21 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] |
| ... | @@ -28,7 +27,7 @@ declare module 'vue' { | ... | @@ -28,7 +27,7 @@ declare module 'vue' { |
| 28 | OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default'] | 27 | OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default'] |
| 29 | PdfPreview: typeof import('./src/components/PdfPreview.vue')['default'] | 28 | PdfPreview: typeof import('./src/components/PdfPreview.vue')['default'] |
| 30 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | 29 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] |
| 31 | - PlanPopup: typeof import('./src/components/PlanSchemes/PlanPopup.vue')['default'] | 30 | + PlanPopup: typeof import('./src/components/PlanPopup/index.vue')['default'] |
| 32 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | 31 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] |
| 33 | QrCode: typeof import('./src/components/qrCode.vue')['default'] | 32 | QrCode: typeof import('./src/components/qrCode.vue')['default'] |
| 34 | QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] | 33 | QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | - <view | 2 | + <view class="search-bar" :class="containerClass"> |
| 3 | - class="search-bar flex items-center" | 3 | + <!-- NutUI Input 组件 --> |
| 4 | - :class="containerClass" | 4 | + <nut-input |
| 5 | - > | ||
| 6 | - <!-- Search Icon --> | ||
| 7 | - <IconFont | ||
| 8 | - name="search" | ||
| 9 | - :size="iconSize" | ||
| 10 | - :color="iconColor" | ||
| 11 | - class="flex-shrink-0 mr-[16rpx]" | ||
| 12 | - /> | ||
| 13 | - | ||
| 14 | - <!-- Input --> | ||
| 15 | - <input | ||
| 16 | v-model="internalValue" | 5 | v-model="internalValue" |
| 17 | :type="inputType" | 6 | :type="inputType" |
| 18 | :placeholder="placeholder" | 7 | :placeholder="placeholder" |
| 19 | - :placeholder-class="placeholderClass" | ||
| 20 | - :class="inputClass" | ||
| 21 | :disabled="disabled" | 8 | :disabled="disabled" |
| 22 | - @focus="handleFocus" | 9 | + confirm-type="search" |
| 10 | + :clearable="showClear" | ||
| 11 | + class="search-input" | ||
| 12 | + @clear="handleClear" | ||
| 23 | @blur="handleBlur" | 13 | @blur="handleBlur" |
| 24 | - @input="handleInput" | 14 | + @focus="handleFocus" |
| 25 | @confirm="handleSearch" | 15 | @confirm="handleSearch" |
| 26 | - /> | 16 | + > |
| 27 | - | 17 | + <template #left> |
| 28 | - <!-- Clear Button --> | ||
| 29 | <IconFont | 18 | <IconFont |
| 30 | - v-if="showClear && internalValue" | 19 | + name="search" |
| 31 | - name="close" | 20 | + :size="iconSize" |
| 32 | - :size="clearIconSize" | 21 | + :color="iconColor" |
| 33 | - :color="clearIconColor" | 22 | + class="mr-[8rpx]" |
| 34 | - class="flex-shrink-0 ml-[16rpx]" | ||
| 35 | - @tap="handleClear" | ||
| 36 | /> | 23 | /> |
| 24 | + </template> | ||
| 25 | + </nut-input> | ||
| 37 | </view> | 26 | </view> |
| 38 | </template> | 27 | </template> |
| 39 | 28 | ||
| ... | @@ -42,9 +31,9 @@ import { ref, watch, computed } from 'vue' | ... | @@ -42,9 +31,9 @@ import { ref, watch, computed } from 'vue' |
| 42 | import IconFont from '@/components/IconFont.vue' | 31 | import IconFont from '@/components/IconFont.vue' |
| 43 | 32 | ||
| 44 | /** | 33 | /** |
| 45 | - * SearchBar 组件 | 34 | + * SearchBar 组件(基于 NutUI Input) |
| 46 | * | 35 | * |
| 47 | - * @description 可复用的搜索栏组件,支持多种样式变体 | 36 | + * @description 可复用的搜索栏组件,使用 NutUI Input 组件实现 |
| 48 | * @author Claude Code | 37 | * @author Claude Code |
| 49 | * @example | 38 | * @example |
| 50 | * <SearchBar | 39 | * <SearchBar |
| ... | @@ -136,24 +125,6 @@ const props = defineProps({ | ... | @@ -136,24 +125,6 @@ const props = defineProps({ |
| 136 | iconColor: { | 125 | iconColor: { |
| 137 | type: String, | 126 | type: String, |
| 138 | default: '#9CA3AF' | 127 | default: '#9CA3AF' |
| 139 | - }, | ||
| 140 | - /** | ||
| 141 | - * 清除图标大小 | ||
| 142 | - * @type {number|string} | ||
| 143 | - * @default 16 | ||
| 144 | - */ | ||
| 145 | - clearIconSize: { | ||
| 146 | - type: [Number, String], | ||
| 147 | - default: 16 | ||
| 148 | - }, | ||
| 149 | - /** | ||
| 150 | - * 清除图标颜色 | ||
| 151 | - * @type {string} | ||
| 152 | - * @default '#9CA3AF' | ||
| 153 | - */ | ||
| 154 | - clearIconColor: { | ||
| 155 | - type: String, | ||
| 156 | - default: '#9CA3AF' | ||
| 157 | } | 128 | } |
| 158 | }) | 129 | }) |
| 159 | 130 | ||
| ... | @@ -198,41 +169,17 @@ const internalValue = ref(props.modelValue) | ... | @@ -198,41 +169,17 @@ const internalValue = ref(props.modelValue) |
| 198 | 169 | ||
| 199 | // 容器样式类 | 170 | // 容器样式类 |
| 200 | const containerClass = computed(() => { | 171 | const containerClass = computed(() => { |
| 201 | - const base = [ | 172 | + const classes = ['search-bar'] |
| 202 | - 'bg-white', | ||
| 203 | - 'shadow-sm', | ||
| 204 | - 'px-[40rpx]', // 左右 padding | ||
| 205 | - props.variant === 'rounded' ? 'rounded-full' : 'rounded-[20rpx]' | ||
| 206 | - ] | ||
| 207 | - | ||
| 208 | - if (props.showBorder) { | ||
| 209 | - base.push('border', 'border-gray-200') | ||
| 210 | - } else { | ||
| 211 | - base.push('border', 'border-gray-50') | ||
| 212 | - } | ||
| 213 | 173 | ||
| 214 | - // 高度样式 | ||
| 215 | if (props.variant === 'rounded') { | 174 | if (props.variant === 'rounded') { |
| 216 | - base.push('h-[88rpx]') | 175 | + classes.push('search-bar-rounded') |
| 217 | - } else { | ||
| 218 | - base.push('py-[24rpx]') | ||
| 219 | } | 176 | } |
| 220 | 177 | ||
| 221 | - return base.join(' ') | 178 | + if (props.showBorder) { |
| 222 | -}) | 179 | + classes.push('search-bar-bordered') |
| 223 | - | 180 | + } |
| 224 | -// 占位符样式类 | ||
| 225 | -const placeholderClass = 'text-gray-400 text-[28rpx]' | ||
| 226 | 181 | ||
| 227 | -// 输入框样式类 | 182 | + return classes.join(' ') |
| 228 | -const inputClass = computed(() => { | ||
| 229 | - return [ | ||
| 230 | - 'flex-1', | ||
| 231 | - 'text-[28rpx]', | ||
| 232 | - 'bg-transparent', | ||
| 233 | - 'outline-none', | ||
| 234 | - props.disabled ? 'opacity-50' : '' | ||
| 235 | - ].filter(Boolean).join(' ') | ||
| 236 | }) | 183 | }) |
| 237 | 184 | ||
| 238 | // 监听 modelValue 变化 | 185 | // 监听 modelValue 变化 |
| ... | @@ -243,12 +190,14 @@ watch(() => props.modelValue, (newValue) => { | ... | @@ -243,12 +190,14 @@ watch(() => props.modelValue, (newValue) => { |
| 243 | // 监听内部值变化,触发更新 | 190 | // 监听内部值变化,触发更新 |
| 244 | watch(internalValue, (newValue) => { | 191 | watch(internalValue, (newValue) => { |
| 245 | emit('update:modelValue', newValue) | 192 | emit('update:modelValue', newValue) |
| 193 | + emit('input', newValue) | ||
| 246 | }) | 194 | }) |
| 247 | 195 | ||
| 248 | /** | 196 | /** |
| 249 | * 处理获得焦点 | 197 | * 处理获得焦点 |
| 250 | */ | 198 | */ |
| 251 | function handleFocus() { | 199 | function handleFocus() { |
| 200 | + console.log('[SearchBar Component] 获得焦点') | ||
| 252 | emit('focus') | 201 | emit('focus') |
| 253 | } | 202 | } |
| 254 | 203 | ||
| ... | @@ -256,27 +205,25 @@ function handleFocus() { | ... | @@ -256,27 +205,25 @@ function handleFocus() { |
| 256 | * 处理失去焦点 | 205 | * 处理失去焦点 |
| 257 | */ | 206 | */ |
| 258 | function handleBlur() { | 207 | function handleBlur() { |
| 208 | + console.log('[SearchBar Component] 失去焦点') | ||
| 259 | emit('blur') | 209 | emit('blur') |
| 260 | } | 210 | } |
| 261 | 211 | ||
| 262 | /** | 212 | /** |
| 263 | - * 处理输入 | ||
| 264 | - */ | ||
| 265 | -function handleInput(e) { | ||
| 266 | - emit('input', internalValue.value) | ||
| 267 | -} | ||
| 268 | - | ||
| 269 | -/** | ||
| 270 | * 处理搜索(回车) | 213 | * 处理搜索(回车) |
| 271 | */ | 214 | */ |
| 272 | function handleSearch() { | 215 | function handleSearch() { |
| 216 | + console.log('[SearchBar Component] handleSearch 被调用') | ||
| 217 | + console.log('[SearchBar Component] 当前输入值:', internalValue.value) | ||
| 273 | emit('search', internalValue.value) | 218 | emit('search', internalValue.value) |
| 219 | + console.log('[SearchBar Component] search 事件已发送') | ||
| 274 | } | 220 | } |
| 275 | 221 | ||
| 276 | /** | 222 | /** |
| 277 | * 清除输入 | 223 | * 清除输入 |
| 278 | */ | 224 | */ |
| 279 | function handleClear() { | 225 | function handleClear() { |
| 226 | + console.log('[SearchBar Component] 清除输入') | ||
| 280 | internalValue.value = '' | 227 | internalValue.value = '' |
| 281 | emit('clear') | 228 | emit('clear') |
| 282 | emit('update:modelValue', '') | 229 | emit('update:modelValue', '') |
| ... | @@ -284,5 +231,30 @@ function handleClear() { | ... | @@ -284,5 +231,30 @@ function handleClear() { |
| 284 | </script> | 231 | </script> |
| 285 | 232 | ||
| 286 | <style lang="less" scoped> | 233 | <style lang="less" scoped> |
| 287 | -/* 样式通过 TailwindCSS 类控制 */ | 234 | +.search-bar { |
| 235 | + padding: 0; | ||
| 236 | + background: white; | ||
| 237 | + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); | ||
| 238 | + | ||
| 239 | + :deep(.nut-input) { | ||
| 240 | + padding: 24rpx 32rpx; | ||
| 241 | + background-color: transparent; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + &.search-bar-rounded { | ||
| 245 | + border-radius: 9999rpx; | ||
| 246 | + | ||
| 247 | + :deep(.nut-input) { | ||
| 248 | + border-radius: 9999rpx; | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + &.search-bar-bordered { | ||
| 253 | + border: 1px solid #e5e7eb; | ||
| 254 | + } | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +.search-bar-rounded { | ||
| 258 | + height: 88rpx; | ||
| 259 | +} | ||
| 288 | </style> | 260 | </style> | ... | ... |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment