需求分析
- 展示切换动画
- 搜索框输入文字,自动发送请求
- 搜索结果展示
- 搜索状态维护
- 历史搜索展示,点击历史搜索后发送请求
- 历史搜索更多切换动画
- 效果
<script setup lang="ts"> import OpSearch from '@/components/OpSearch.vue' import { ref } from 'vue' import { fetchSearchData } from '@/api/search' import type { ISearchResult } from '@/types' import { useToggle } from '@/use/useToggle' import { computed } from 'vue' import { watch } from 'vue' // 声明事件接口,接口中属性值是一个函数,函数名是cancel,返回值是一个函数void interface IEmits { (e: 'cancel'): void } const searchValue = ref('') const searchResult = ref([] as ISearchResult[]) // 定义一个事件变量,用defineEmits方法实现,方法中引入声明的事件接口 const emits = defineEmits<IEmits>() const HISTORY_TAGS = [ '披萨', '标签2', '标签3', '标签4', '标签5', '标签6', '标签7', ] const [isHistoryTagShown, toggleHistoryTag] = useToggle(false) const historyTags = computed(() => (isHistoryTagShown.value ? HISTORY_TAGS : HISTORY_TAGS.slice(0, 5))) // 有三种状态:搜索初始化、搜索完成、搜索中 const [INIT, DONE, DOING] = [-1, 0, 1] const searchState = ref(INIT) const onSearch = async (v?: string | number) => { console.log('onSearch', v) // 防止搜索状态错误 try { searchState.value = DOING const { list } = await fetchSearchData(v as string) searchResult.value = list } finally { searchState.value = DONE } } const onTagClick = (v:string) => { searchValue.value = v onSearch(v) } watch(searchValue, (new_v) => { if(!new_v) { searchResult.value = [] return } onSearch(new_v) }) </script> <template> <!-- 调用事件变量,传入事件名cancel // 模板代码中引入定义的事件,用来在父组件中使用对应的事件 --> <div class="search-view"> <OpSearch show-action v-model="searchValue" shape="round" placeholder="请输入搜索关键词" @search="onSearch" @cancel="emits('cancel')" /> <div v-if="!searchValue" class="search-view__history"> <div class="label">历史搜索</div> <TransitionGroup name="list"> <div class="history-tag" v-for="v in historyTags" :key="v" @click="onTagClick(v)">{{ v }}</div> <div class="history-tag" key="arrow" @click="toggleHistoryTag"> <VanIcon v-if="isHistoryTagShown" name="arrow-up"></VanIcon> <VanIcon v-else name="arrow-down"></VanIcon> </div> </TransitionGroup> </div> <div v-else class="search-view__result"> <div class="searching" v-if="searchState === DOING">~正在搜索</div> <template v-if="searchState === DONE"> <div class="result-item" v-for="v in searchResult" :key="v.label"> <VanIcon name="search"></VanIcon> <div class="name">{{ v.label }}</div> <div class="count">约{{ v.resultCount }}个结果</div> </div> <!-- 搜索结果状态维护 --> <div class="no-result" v-if="!searchResult.length">~暂无推荐</div> </template> </div> </div> </template> <style lang="scss"> .search-view { position: absolute; top: 0; bottom: 0; right: 0; left: 0; background-color: white; z-index: 999; &__history { padding: var(--van-padding-sm); .label { margin-bottom: var(--van-padding-xs); } .history-tag { display: inline-block; font-size: 12px; border-radius: 10px; color: var(--van-gray-6); background: var(--van-gray-1); padding: 4px 8px; margin-right: 10px; margin-bottom: var(--van-padding-xs); } } &__result { .result-item { display: flex; align-items: center; font-size: 12px; padding: 10px; border-radius: 1px solid var(--van-gray-1); .name { // 撑满满足padding的一行 flex: 1; padding-left: 6px; } .count { font-size: 12px; color: var(--van-gray-6); } } .no-result, .searching { font-size: 12px; padding: 100px 0; text-align: center; color: var(--van-gray-6) } } } .list-enter-active, .list-leave-active { transition: all 1s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateY(30px); } </style>
使用 <transition>和 <transition-group> 实现动画效果
使用
- 在
<transition>
组件中,你可以使用name
属性来指定动画的类名,在CSS中定义类名,并为其添加过渡效果
<transition>
<template> <!-- 动画组件使用方法 --> <Transition name="fade"> <SearchView v-if="isSearchViewShown" @cancel="toggleSearchView"></SearchView> </Transition> </template> <style lang="scss"> // 动画执行效果,消失效果 .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } // 动画进行时状态效果 .fade-enter-from, .fade-leave-to { opacity: 0; } </style>
<transition-group>
<template> <TransitionGroup name="list"> // 组件里内容使用了v-for,是数组形式 <div class="history-tag" v-for="v in historyTags" :key="v" @click="onTagClick(v)">{{ v }}</div> <div class="history-tag" key="arrow" @click="toggleHistoryTag"> <VanIcon v-if="isHistoryTagShown" name="arrow-up"></VanIcon> <VanIcon v-else name="arrow-down"></VanIcon> </div> </TransitionGroup> </template> <style lang="scss"> // 定义动画css样式 .list-enter-active, .list-leave-active { transition: all 1s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateY(30px); } </style>
Search 组件复用
- 将之前章节写好的OpSearch组件复用到SearchView组件中
<script setup lang="ts"> //引入组件 import OpSearch from '@/components/OpSearch.vue' import { ref } from 'vue' const onSearch = async (v?: string | number) => { console.log('onSearch', v) } // 定义搜索输入框里的参数变量 const searchValue = ref('') // 声明事件接口,接口中属性值是一个函数,函数名是cancel,返回值是一个函数void interface IEmits { (e: 'cancel'): void } // 定义一个事件变量,用defineEmits方法实现,方法中引入声明的事件接口 const emits = defineEmits<IEmits>() </script> <template> // 使用组件 <OpSearch show-action //对变量searchValue值进行双向绑定 v-model="searchValue" shape="round" placeholder="请输入搜索关键词" // 创建onSearch方法 @search="onSearch" //定义cancel事件 @cancel="emits('cancel')" /> </template>
computed 计算属性
理解
- 方便地计算和监听数据的变化。
<script setup lang="ts"> import { useToggle } from '@/use/useToggle' import { computed } from 'vue' const HISTORY_TAGS = [ '披萨', '标签2', '标签3', '标签4', '标签5', '标签6', '标签7', ] const [isHistoryTagShown, toggleHistoryTag] = useToggle(false) const historyTags = computed(() => (isHistoryTagShown.value ? HISTORY_TAGS : HISTORY_TAGS.slice(0, 5))) <template> <div class="history-tag" key="arrow" @click="toggleHistoryTag"> <VanIcon v-if="isHistoryTagShown" name="arrow-up"></VanIcon> <VanIcon v-else name="arrow-down"></VanIcon> </div> </template>
watch 监听属性
理解
watch
函数接受两个参数:一个是要监听的参数,以及一个回调函数。回调函数触发的前提是,当被监听的参数发生变化时,回调函数将被执行。<script setup lang="ts"> // 引入watch函数 import { watch } from 'vue' // watch监听函数的使用方法,监听searchValue参数又叫属性值的变化,有变动时就会触发回调函数中的代码。 watch(searchValue, (new_v) => { if(!new_v) { searchResult.value = [] return } onSearch(new_v) }) </script>
使用
axios实例发送业务请求
- 开发环境配置反向代理使用服务接口
- 设置请求响应拦截
- 创建具体功能请求函数
- 调用功能请求函数
mock 请求:
看这篇文章 使用apifox创建一个Mock Server Api 接口-CSDN博客