feat(SearchBar): 改进搜索栏兼容性并优化课程页交互
- 将输入框类型改为search并添加表单提交处理,提升iOS设备兼容性 - 课程详情页增加咨询按钮显示并优化课程大纲条件渲染
Showing
3 changed files
with
73 additions
and
16 deletions
| ... | @@ -54,3 +54,9 @@ https://oa-dev.onwall.cn/f/mlaj | ... | @@ -54,3 +54,9 @@ https://oa-dev.onwall.cn/f/mlaj |
| 54 | - 行为:接口返回 `code=401` 时,不再对公开页面(如课程详情 `/courses/:id`)进行登录重定向;仅当当前路由确实需要登录权限时才跳转至登录页。 | 54 | - 行为:接口返回 `code=401` 时,不再对公开页面(如课程详情 `/courses/:id`)进行登录重定向;仅当当前路由确实需要登录权限时才跳转至登录页。 |
| 55 | - 原理:响应拦截器调用路由守卫 `checkAuth` 判断当前路由是否为受限页面,受限则清理登录信息并附带 `redirect` 重定向至登录页;公开页面保持当前页,由业务自行处理401。 | 55 | - 原理:响应拦截器调用路由守卫 `checkAuth` 判断当前路由是否为受限页面,受限则清理登录信息并附带 `redirect` 重定向至登录页;公开页面保持当前页,由业务自行处理401。 |
| 56 | - 位置:`/src/utils/axios.js`,在响应拦截器中按需处理重定向。 | 56 | - 位置:`/src/utils/axios.js`,在响应拦截器中按需处理重定向。 |
| 57 | + - 搜索栏回车搜索兼容性提升 | ||
| 58 | + - 行为:将输入框类型改为 `search`,并可选开启 `form @submit.prevent` 机制以提升 iOS 设备软键盘“搜索”键触发稳定性;同时保留 `@keyup.enter` 回车触发搜索。 | ||
| 59 | + - 路由行为: | ||
| 60 | + - 课程页(`isCoursePage=true`):回车或搜索键触发后跳转至 `/courses-list` 并携带 `keyword` 参数。 | ||
| 61 | + - 列表页:仅更新当前路由的查询参数 `keyword`。 | ||
| 62 | + - 位置:`/src/components/ui/SearchBar.vue`,新增 `useFormSubmit` 可选参数(默认开启)。 | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | - <FrostedGlass class="px-4 py-2 mx-4 my-3 flex items-center"> | 2 | + <FrostedGlass class="px-4 py-2 mx-4 my-3 flex items-center"> |
| 3 | - <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | 3 | + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 4 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> | 4 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> |
| 5 | - </svg> | 5 | + </svg> |
| 6 | - <input | 6 | + <template v-if="props.useFormSubmit"> |
| 7 | - type="text" | 7 | + <form @submit.prevent="handleSubmit" action="javascript:return true" class="flex-1"> |
| 8 | - :placeholder="placeholder" | 8 | + <input |
| 9 | - v-model="localValue" | 9 | + type="search" |
| 10 | - class="bg-transparent outline-none flex-1 text-gray-700 placeholder-gray-400" | 10 | + :placeholder="placeholder" |
| 11 | - @input="handleSearch" | 11 | + v-model="localValue" |
| 12 | - @blur="handleBlur" | 12 | + class="bg-transparent outline-none w-full text-gray-700 placeholder-gray-400" |
| 13 | - /> | 13 | + @input="handleSearch" |
| 14 | - </FrostedGlass> | 14 | + @blur="handleBlur" |
| 15 | + @keyup.enter="handleEnter" | ||
| 16 | + /> | ||
| 17 | + </form> | ||
| 18 | + </template> | ||
| 19 | + <template v-else> | ||
| 20 | + <input | ||
| 21 | + type="search" | ||
| 22 | + :placeholder="placeholder" | ||
| 23 | + v-model="localValue" | ||
| 24 | + class="bg-transparent outline-none flex-1 text-gray-700 placeholder-gray-400" | ||
| 25 | + @input="handleSearch" | ||
| 26 | + @blur="handleBlur" | ||
| 27 | + @keyup.enter="handleEnter" | ||
| 28 | + /> | ||
| 29 | + </template> | ||
| 30 | + </FrostedGlass> | ||
| 15 | </template> | 31 | </template> |
| 16 | 32 | ||
| 17 | <script setup> | 33 | <script setup> |
| ... | @@ -33,6 +49,10 @@ const props = defineProps({ | ... | @@ -33,6 +49,10 @@ const props = defineProps({ |
| 33 | isCoursePage: { | 49 | isCoursePage: { |
| 34 | type: Boolean, | 50 | type: Boolean, |
| 35 | default: false | 51 | default: false |
| 52 | + }, | ||
| 53 | + useFormSubmit: { | ||
| 54 | + type: Boolean, | ||
| 55 | + default: true | ||
| 36 | } | 56 | } |
| 37 | }) | 57 | }) |
| 38 | 58 | ||
| ... | @@ -44,11 +64,23 @@ watch(() => props.modelValue, (newValue) => { | ... | @@ -44,11 +64,23 @@ watch(() => props.modelValue, (newValue) => { |
| 44 | localValue.value = newValue | 64 | localValue.value = newValue |
| 45 | }) | 65 | }) |
| 46 | 66 | ||
| 67 | +/** | ||
| 68 | + * @function handleSearch | ||
| 69 | + * @description 输入变更时触发搜索事件与 v-model 同步(即时搜索)。 | ||
| 70 | + * @returns {void} | ||
| 71 | + */ | ||
| 47 | const handleSearch = () => { | 72 | const handleSearch = () => { |
| 48 | emit('update:modelValue', localValue.value) | 73 | emit('update:modelValue', localValue.value) |
| 49 | emit('search', localValue.value) | 74 | emit('search', localValue.value) |
| 50 | } | 75 | } |
| 51 | 76 | ||
| 77 | +/** | ||
| 78 | + * @function handleBlur | ||
| 79 | + * @description 失焦时根据页面类型进行跳转或更新 URL 参数。 | ||
| 80 | + * - 课程页:跳转至课程列表页并携带关键字参数。 | ||
| 81 | + * - 列表页:仅更新当前路由的查询参数。 | ||
| 82 | + * @returns {void} | ||
| 83 | + */ | ||
| 52 | const handleBlur = () => { | 84 | const handleBlur = () => { |
| 53 | const query = localValue.value | 85 | const query = localValue.value |
| 54 | emit('blur', query) | 86 | emit('blur', query) |
| ... | @@ -64,4 +96,23 @@ const handleBlur = () => { | ... | @@ -64,4 +96,23 @@ const handleBlur = () => { |
| 64 | router.replace({ query: { ...router.currentRoute.value.query, keyword: localValue.value } }) | 96 | router.replace({ query: { ...router.currentRoute.value.query, keyword: localValue.value } }) |
| 65 | } | 97 | } |
| 66 | } | 98 | } |
| 99 | + | ||
| 100 | +/** | ||
| 101 | + * @function handleEnter | ||
| 102 | + * @description 键盘回车触发与失焦同等的搜索行为,保证“回车搜索”有效。 | ||
| 103 | + * @returns {void} | ||
| 104 | + */ | ||
| 105 | +const handleEnter = () => { | ||
| 106 | + // 复用失焦逻辑以保持一致的路由行为 | ||
| 107 | + handleBlur() | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +/** | ||
| 111 | + * @function handleSubmit | ||
| 112 | + * @description 表单提交(移动端键盘“搜索”)触发与失焦同等的搜索行为,增强 iOS 兼容性。 | ||
| 113 | + * @returns {void} | ||
| 114 | + */ | ||
| 115 | +const handleSubmit = () => { | ||
| 116 | + handleBlur() | ||
| 117 | +} | ||
| 67 | </script> | 118 | </script> | ... | ... |
| ... | @@ -49,7 +49,7 @@ | ... | @@ -49,7 +49,7 @@ |
| 49 | </FrostedGlass> --> | 49 | </FrostedGlass> --> |
| 50 | 50 | ||
| 51 | <!-- Tab Navigation --> | 51 | <!-- Tab Navigation --> |
| 52 | - <FrostedGlass class="mb-6 rounded-xl overflow-hidden"> | 52 | + <FrostedGlass v-if="curriculumItems.length" class="mb-6 rounded-xl overflow-hidden"> |
| 53 | <div class="border-b border-gray-200"> | 53 | <div class="border-b border-gray-200"> |
| 54 | <div class="flex"> | 54 | <div class="flex"> |
| 55 | <button v-for="(item, index) in curriculumItems" :key="index" @click="activeTab = item.title" :class="[ | 55 | <button v-for="(item, index) in curriculumItems" :key="index" @click="activeTab = item.title" :class="[ |
| ... | @@ -223,7 +223,7 @@ | ... | @@ -223,7 +223,7 @@ |
| 223 | </svg> | 223 | </svg> |
| 224 | 收藏 | 224 | 收藏 |
| 225 | </button> | 225 | </button> |
| 226 | - <!-- <button class="flex flex-col items-center text-gray-500 text-xs" @click="open_consult_dialog"> | 226 | + <button class="flex flex-col items-center text-gray-500 text-xs" @click="open_consult_dialog"> |
| 227 | <svg | 227 | <svg |
| 228 | xmlns="http://www.w3.org/2000/svg" | 228 | xmlns="http://www.w3.org/2000/svg" |
| 229 | class="h-6 w-6" | 229 | class="h-6 w-6" |
| ... | @@ -239,7 +239,7 @@ | ... | @@ -239,7 +239,7 @@ |
| 239 | /> | 239 | /> |
| 240 | </svg> | 240 | </svg> |
| 241 | 咨询 | 241 | 咨询 |
| 242 | - </button> --> | 242 | + </button> |
| 243 | </div> | 243 | </div> |
| 244 | <div class="flex items-center"> | 244 | <div class="flex items-center"> |
| 245 | <div v-if="!course?.is_buy" class="mr-2"> | 245 | <div v-if="!course?.is_buy" class="mr-2"> | ... | ... |
-
Please register or login to post a comment