hookehuyr

feat(SearchBar): 改进搜索栏兼容性并优化课程页交互

- 将输入框类型改为search并添加表单提交处理,提升iOS设备兼容性
- 课程详情页增加咨询按钮显示并优化课程大纲条件渲染
...@@ -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">
......