【前端】Vue项目:旅游App-(14)home+search:搜索按钮及其路由跳转、分组数据的网络请求request、数据存储store和动态显示

news2025/1/15 21:07:15

文章目录

    • 目标
    • 过程与代码
      • 搜索部分:
      • 搜索按钮
      • 点击搜索按钮路由跳转并传数据
      • search页面隐藏TabBar
      • 分类部分:
      • 数据请求:request、store
      • 显示数据
      • 分类的样式
    • 总代码
      • 修改或添加的文件
      • common.css
      • router的index.js
      • service的home.js
      • store的home.js
      • home-categories.vue
      • home.vue
      • search.vue
      • App.vue
    • 参考

本项目博客总结:【前端】Vue项目:旅游App-博客总结

目标

在这里插入图片描述

  • 搜索按钮,点击后跳转至search页面(只是跳转)
  • 动态显示下面的分类,数据来自123.207.32.32:1888/api/home/categories

过程与代码

搜索部分:

搜索按钮

在common.css中设置按钮的渐变色:

/* 渐变色 */
--theme-linear-gradient:linear-gradient(90deg,#fa8c1d,#fcaf3f);

html:

<div class="searchBtn">
    开始搜索
</div>

css:

.searchBtn{
    display: flex;
    justify-content: center;
    align-items: center;

    height: 38px;
    font-size: 18px;
    // 渐变色要用image
    background-image:var(--theme-linear-gradient);
    color: #fff;

    border-radius: 20px;
    margin: 20px 20px;
}

效果:

在这里插入图片描述

点击搜索按钮路由跳转并传数据

先写一个search.vue:

<template>
    <div class="search">
        <h2>search</h2>
    </div>
</template>

<script setup>

</script>

<style lang="less" scoped>

</style>

注册路由:

{
   path:'/search',
   component:()=>import("@/views/search/search.vue")
}

在按钮的地方监听点击:

<div class="searchBtn" @click="searchBtnClick()">
   开始搜索
</div>

对应js:

import { useRouter } from 'vue-router'
const router = useRouter()
// 搜索按钮跳转
function searchBtnClick() {
    router.push({
        path:'/search'
    })
}

到这里,点击了之后就可以跳转至search页面了。

一般来说,点击后的跳转会携带一些数据,我们这里用query属性来携带数据。

在home.vue传送数据:

// 搜索按钮跳转
function searchBtnClick() {
    router.push({
        path:'/search',
        query:{
            // 因为是响应式
            startDay:startDay.value,
            endDay:endDay.value
        }
    })
}

在search页面用$route.query显示数据:

<div class="search">
    <h2>search</h2>
    <h2>{{ $route.query.startDay }}
        {{ $route.query.endDay }}</h2>
</div>

效果:
在这里插入图片描述

search页面隐藏TabBar

在之前的博客中我们写了两种隐藏TabBar的方法。前面用的是第二种,这里我们用第一种。

ps:两种方法都可以,工作中具体看哪种更方便就选哪种,这里只是练习。

在路由处设置meta:

{
    path: '/search',
    component: () => import("@/views/search/search.vue"),
    meta: {
        hideTabBar: true
    }
}

在App.vue处增加v-if的判断:

<tab-bar v-if="!$route.meta.hideTabBar"></tab-bar>

js:引入route

import { useRoute } from 'vue-router'

const route=useRoute()

效果:可以隐藏。

在这里插入图片描述

分类部分:

数据请求:request、store

在本项目中已经进行了两次数据请求,因此具体步骤不再赘述。

接口:123.207.32.32:1888/api/home/categories

在service/home中:

export function getCategories() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url: '/home/categories'
    })
}

在store/home中:

const useHomeStore = defineStore('home', {
    state: () => {
        return {
            hotSuggest: [],
            categories:[]
        }
    },
    actions: {
        // 网络请求,由于返回一个promise,要异步async await
        async fetchHotSuggest() {
            const res = await getHotSuggest()
            this.hotSuggest = res.data
            // console.log(res)
        },
        async fetchCategories(){
            const res = await getCategories()
            this.categories = res.data
        }
    }
})

控制台输出一下数据:

在这里插入图片描述
在这里插入图片描述
数据请求成功。

显示数据

home-categories组件:

<template>
    <div class="home-categories">
        <!-- 一般有id就让key绑定id -->
        <template v-for="(item, index) in categoriesData" :key="item.id">
            <div class="item">
                <img :src=item.pictureUrl alt="">
                <div class="text">{{ item.title }}</div>
            </div>
        </template>
    </div>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import useHomeStore from '../../../store/modules/home';

const homeStore = useHomeStore()
homeStore.fetchCategories()
const { categories } = storeToRefs(homeStore)
const categoriesData = categories
</script>

<style lang="less" scoped>

</style>

效果:能显示,但要加些样式
在这里插入图片描述

分类的样式

注意:让左右可以滚动但滚动条不显示的方法:

// 滚动条
overflow-x: auto;

// 滚动条不显示
&::-webkit-scrollbar {
    display: none;
}

整体css:

.home-categories {
    display: flex;
    align-items: center;
    padding: 0 20px;
    margin-top: 20px;
    margin-left: -10px;
    margin-bottom: 10px;
    height: 65px;

    // 滚动条
    overflow-x: auto;

    // 滚动条不显示
    &::-webkit-scrollbar {
        display: none;
    }

    .item {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 0 12px;

        img {
            width: 32px;
            height: 32px;
        }

        .text {
            height: 20px;
            width: 56px;
            text-align: center;
        }
    }
}

效果:

在这里插入图片描述

总代码

修改或添加的文件

在这里插入图片描述

在这里插入图片描述

common.css

添加渐变色。

:root {
    /* 渐变色 */
    --theme-linear-gradient:linear-gradient(90deg,#fa8c1d,#fcaf3f);
}

router的index.js

添加search页面的路由。

{
   path: '/search',
   component: () => import("@/views/search/search.vue"),
   meta: {
       hideTabBar: true
   }
}

service的home.js

添加对分类数据categories的网络请求。

export function getCategories() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url: '/home/categories'
    })
}

store的home.js

添加对分类数据categories的store数据存储。

// home.vue页面所有的进行网络请求和数据都封装到这里

import { defineStore } from "pinia";
import { getHotSuggest,getCategories } from '@/service'

const useHomeStore = defineStore('home', {
    state: () => {
        return {
            hotSuggest: [],
            categories:[]
        }
    },
    actions: {
        // 网络请求,由于返回一个promise,要异步async await
        async fetchHotSuggest() {
            const res = await getHotSuggest()
            this.hotSuggest = res.data
            // console.log(res)
        },
        async fetchCategories(){
            const res = await getCategories()
            this.categories = res.data
        }
    }
})

export default useHomeStore

home-categories.vue

封装了分类数据categories页面的组件。

<template>
    <div class="home-categories">
        <!-- 一般有id就让key绑定id -->
        <template v-for="(item, index) in categoriesData" :key="item.id">
            <div class="item">
                <img :src=item.pictureUrl alt="">
                <div class="text">{{ item.title }}</div>
            </div>
        </template>
    </div>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import useHomeStore from '../../../store/modules/home';

const homeStore = useHomeStore()
homeStore.fetchCategories()
const { categories } = storeToRefs(homeStore)
const categoriesData = categories
</script>

<style lang="less" scoped>
.home-categories {
    display: flex;
    align-items: center;
    padding: 0 20px;
    margin-top: 20px;
    margin-left: -10px;
    margin-bottom: 10px;
    height: 65px;

    // 滚动条
    overflow-x: auto;

    // 滚动条不显示
    &::-webkit-scrollbar {
        display: none;
    }

    .item {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 0 12px;

        img {
            width: 32px;
            height: 32px;
        }

        .text {
            height: 20px;
            width: 56px;
            text-align: center;
        }
    }
}
</style>

home.vue

增加了search和categories的显示。

<template>
    <div class="home">
        <div class="nav-bar">
            <div class="title">旅游App</div>
            <div class="banner">
                <img src="@/assets/img/home/banner.webp" alt="">
            </div>
        </div>
        <div class="search-box">
            <div class="section location">
                <div class="city">
                    <router-link to="/city">{{ cityStore.currentCity.cityName }}</router-link>
                </div>
                <div class="position">
                    <div class="text">我的位置</div>
                    <img src="@/assets/img/home/icon_location.png" alt="">
                </div>
            </div>

            <div class="section time-range" :value="date" @click="showCalendar = true">
                <div class="start">
                    <span>入住</span>
                    <div class="time">
                        {{ startDay }}
                    </div>
                </div>
                <div class="stay">共{{ date }}晚</div>
                <div class="end">
                    <span>离店</span>
                    <div class="time">
                        {{ endDay }}
                    </div>
                </div>
            </div>

            <!-- 日历 -->
            <van-calendar :round="false" v-model:show="showCalendar" type="range" @confirm="onConfirm"
                :show-confirm="false" />

            <!-- 价格人数 -->
            <div class="price-counter section">
                <div class="left">价格不限</div>
                <div class="right">人数不限</div>
            </div>

            <!-- 关键字 -->
            <div class="keyword section">
                <span>关键字/位置/民宿名</span>
            </div>

            <!-- 热门推荐 -->
            <div class="hotSuggest section">
                <template v-for="(item, index) in hotSuggestData" :key="index">
                    <div class="hotSuggestItem">
                        {{ item.tagText.text }}
                    </div>
                </template>
            </div>

            <div class="searchBtn" @click="searchBtnClick()">
                开始搜索
            </div>

            <homeCategories />
        </div>
    </div>
</template>

<script setup>
import useCityStore from '../../store/modules/city';
import useHomeStore from '../../store/modules/home';
import { formatMonthDay, getDiffDate } from '@/utils/formatDate'
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router'
import homeCategories from './cpns/home-categories.vue'

const cityStore = useCityStore()
const homeStore = useHomeStore()
const router = useRouter()

homeStore.fetchCategories()
const { categories } = storeToRefs(homeStore)
console.log(categories)

// 日期
const today = new Date()
const startDay = ref(formatMonthDay(today))
const endDay = ref(formatMonthDay(new Date().setDate(today.getDate() + 1))) //明天写法

// 日历
const date = ref('1');
const showCalendar = ref(false);

const formatDate = (date) => `${date.getMonth() + 1}/${date.getDate()}`;
const onConfirm = (values) => {
    const [start, end] = values;
    showCalendar.value = false;

    startDay.value = formatMonthDay(start)
    endDay.value = formatMonthDay(end)
    date.value = getDiffDate(start, end)
};

// 热门数据
homeStore.fetchHotSuggest()
const { hotSuggest } = storeToRefs(homeStore)
const hotSuggestData = hotSuggest

// 搜索按钮跳转
function searchBtnClick() {
    router.push({
        path: '/search',
        query: {
            // 因为是响应式
            startDay: startDay.value,
            endDay: endDay.value
        }
    })
}
</script>

<style lang="less" scoped>
.home {
    .nav-bar {
        .title {
            height: 46px;

            // flex居中,以后左右有东西可以直接加
            display: flex;
            align-items: center;
            justify-content: center;

            color: var(--primary-color);
            font-size: 16px;
            font-weight: 700;
        }

        .banner {

            // 图片本身大很多,让它大小刚好
            img {
                width: 100%;
            }
        }
    }

    .search-box {

        --van-calendar-popup-height: 100%;

        // search-box里的每个部分都加上section
        // 都有类似的样式
        .section {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            padding: 0 20px;
            color: #999;
            margin-top: 10px;
        }

        .location {
            height: 44px;

            display: flex;
            align-items: center;
            padding: 0 20px;
            color: #53565c;

            .city {
                // flex:1 === flex:1 1 auto 除了position之外的剩余部分都属于city
                flex: 1;
            }

            .position {
                width: 74px;

                display: flex;
                align-items: center;

                .text {
                    font-size: 12px;
                }

                img {
                    width: 20px;
                    margin-left: 5px;
                }
            }
        }

        .time-range {
            display: flex;
            justify-content: space-between;
            height: 45px;

            span {
                font-size: 16px;
            }

            .time {
                color: #53565c;
            }
        }

        .price-counter {
            justify-content: space-between;
            height: 35px;
        }

        .keyword {
            height: 35px;
        }

        .hotSuggest {

            .hotSuggestItem {

                margin: 3px;
                padding: 4px 8px;
                font-size: 12px;
                background-color: #f1f3f5;
                color: #3f4954;
                border-radius: 20px;
            }
        }

        .searchBtn {
            display: flex;
            justify-content: center;
            align-items: center;

            height: 38px;
            font-size: 18px;
            // 渐变色要用image
            background-image: var(--theme-linear-gradient);
            color: #fff;

            border-radius: 20px;
            margin: 20px 20px;
        }
    }
}
</style>

search.vue

新增的页面,还没写内容。

<template>
    <div class="search">
        <h2>search</h2>
        <h2>{{ $route.query.startDay }}
            {{ $route.query.endDay }}</h2>
    </div>
</template>

<script setup>

</script>

<style lang="less" scoped>

</style>

App.vue

增加了search的v-if判断是否显示TabBar。

<template>
    <div class="app">
        <router-view/>
        <tab-bar v-if="!$route.meta.hideTabBar"></tab-bar>
    </div>
</template>

<script setup>

import tabBar from './components/tab-bar/tab-bar.vue';
import { useRoute } from 'vue-router'

const route=useRoute()

</script>

<style lang="less" scoped>

</style>

参考

Pinia-学习之路 03,storeToRefs 及 改变数据状态 - 掘金 (juejin.cn)
overflow - CSS(层叠样式表) | MDN (mozilla.org)
::-webkit-scrollbar - CSS(层叠样式表) | MDN (mozilla.org)
【CSS】渐变背景(background-image)_妙趣前端的博客-CSDN博客_background-image 渐变

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows系统下 pyinstaller将python文件打包成可执行文件exe的方法

使用环境为Windows10系统&#xff08;64&#xff09;&#xff0c;Python版本为3.11.1。 1.将pip加入环境变量 &#xff08;1&#xff09;右击此电脑点击"属性"&#xff0c;点击高级系统设置&#xff0c; &#xff08;2&#xff09;选择最下面的环境变量&#xff1b…

C++之继承

文章目录一、继承的基本理解1.继承的概念2.继承的定义二、基类和派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七、复杂的菱形继承及菱形虚拟继承1.继承关系2.菱形继承存在数据冗余和二义性的问题3.虚拟继承可以解决菱形继承…

前端优化原理篇(生命周期)

1&#xff0c; 性能评估模型 对于前端的性能的评判 主要是以下四个方面&#xff1a; 2&#xff0c;性能测量工具 1&#xff0c;浏览器的performarce功能 指路可看链接 2&#xff0c;lighthouse工具 3&#xff0c;生命周期 网站 页面的整个生命周期&#xff0c;通俗的讲&a…

移动端App 页面秒开优化总结

前言 App优化&#xff0c;是一个工作、面试或KPI都绕不开的话题&#xff0c;如何让用户使用流畅呢&#xff1f;今天谨以此篇文章总结一下过去两个月我在工作中的优化事项到底有那些&#xff0c;优化方面还算小白&#xff0c;有不对的地方还望指出海涵, 该文章主要通过讲述Nati…

CSS入门三、盒子模型

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

力扣sql基础篇(四)

力扣sql基础篇(四) 1 每位学生的最高成绩 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # rank()函数间隔排序 若前两个字段值相同且都是并列的第一名,那么后面的一个非连续数字就是第三名 如 1 1 3 SELECT e.student_id,e.course_id,e.grade FROM…

ISO12233分辨率测试卡分类及功能说明

概述相机图像分辨率的测试&#xff0c;依据的标准是ISO 12233. 目前分为 ISO12233:2000 ,ISO12233:4000, ISO12233:2014.目前很多厂家已经开始使用新的ISO标准&#xff0c;淘汰了十几年前的“落伍”标准&#xff0c;而更新成了ISO12233&#xff1a;2014。新的分辨率测试标板是由…

代码随想录算法训练营第四期第五十六天 | 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

583. 两个字符串的删除操作 # 给定两个单词word1和word2&#xff0c;返回使得word1和word2相同所需的最小步数。 # 每步可以删除任意一个字符串中的一个字符。 # # 示例 1&#xff1a; # 输入: word1 "sea", word2 "eat" # 输出: 2 # 解释: 第一步将 &…

安顺控股冲刺A股上市:拟募资6.5亿元,九成收入来自天然气销售

近日&#xff0c;安顺控股股份有限公司&#xff08;下称“安顺控股”&#xff09;递交招股书&#xff0c;准备在上海证券交易所主板上市。本次冲刺上市&#xff0c;安顺控股计划募资6.50亿元&#xff0c;将用于溧阳市城镇燃气高压管网二期项目、溧阳市美丽乡村天然气利用项目一…

openstack私有网络

1.前情回顾 目前环境使用的是Provider网络现在需要将其修改为Self-service网络&#xff0c;类似于公有云的vpc网络 2.流程 1.控制节点配置修改 1.修改/etc/neutron/neutron.conf的[DEFAULT]区域 # 原来的配置 # core_plugin ml2 # service_plugins # # 修改后的配置 c…

GitHub Enterprise Server 存在授权不当漏洞(CVE-2022-46258)

漏洞描述 GitHub Enterprise Server 是一个面向开源及私有软件项目的托管平台&#xff0c;GitHub scope 用于限制 OAuth token 的访问范围。 在 GitHub Enterprise Server 中&#xff0c;除非提交位于同一存储库的不同分支中且和 Workflow files 内容相同的 Workflow 文件 &a…

【学习】Reptile、梯度下降的LSTM、Siamese Network、原型网络、匹配网络、关系网络

文章目录ReptileRNNLSTM梯度下降的LSTM基于度量的方法Siamese NetworkN-way Few/One-shot Learning原型网络匹配网络关系网络虚拟数据的少量学习Train Test as RNNReptile RNN LSTM RNN的变形 加入门 梯度下降的LSTM GD看似像简化的LSTM 可以让机器自动学习这些zf和zi …

sql的where使用运算后的列后报错

sql的where语句中如果使用了经过运算处理后的某个列的话会报错&#xff0c;例如&#xff1a;上面红框部分就是经过运算后的列&#xff0c;但这个语法是错误的&#xff0c;但如果想通过运算后的某个列来筛选条件&#xff0c;应该怎么办&#xff1f;可以使用嵌套查询&#xff1a;…

SpringBoot实践(三十八):自定义spring-boot-starter

目录 自动配置原理 自定义starter包 导入springboot的自动配置依赖 测试业务代码 spring.factories配置 ​编辑 本地包上传 使用自定义starter依赖 测试和配置 自动配置原理 基于springBoot的starter机制能够让我们在使用外部包时候非常方便&#xff0c;只需要引入该组…

PCB学习笔记—3D PCB封装的创建

放置3D元件体&#xff1a;常规的、自定义、圆柱形、球体。第一个是厚度&#xff0c;第二个是焊盘和芯片的悬浮高度。一般电阻的高度设置成0.6mm就够了&#xff0c;电容1.25mm&#xff0c;悬浮高度为0。按键&#xff1a;放置3D元件体&#xff0c;Tab键&#xff0c;选择常规&…

快过年静不下心?不如刷刷《剑指offer》静一静(第七天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 49. 丑数剑指 Offer 51. 数组中的逆序对剑指 Offer 55 - I. 二叉树的深度剑指 Offer 49. 丑数 剑指 Offer 49. 丑数 我一开始的思路是&#xff0c;把数字1~无…

机器学习HW15元学习

文章目录一、简介Task: Few-shot Classification实验1、simple2、medium3、strong4、boss三、代码模型构建准备工作一、简介 任务对象是Omniglot数据集上的few-shot classification任务&#xff0c;内容是利用元学习找到好的初始化参数。 Task: Few-shot Classification The…

在VSCode中使用Compaq Visual Fortran编译运行Frotran程序

本片文章主要是为了使用VSCode编译运行带QuickWin的老版本Fortran代码。 一、准备工作 安装VSCode和Compaq Visual Fortran6.6。 二、配置Fortran工程 用VSCode打开保存有Frotran代码的文件夹 建立.vscode文件夹&#xff0c;建立launch.json和task.json文件&#xff0c;分…

二、TCP/IP---Ethernet和IP协议

TCP/ip协议栈 OSI模型TCP/IP协议栈应用层&#xff0c;表示层&#xff0c;会话层应用层传输层主机到主机层&#xff08;传输层&#xff09;网络层网络层数据链路层&#xff0c;物理层网络接入层 Ethernet协议 以太网&#xff0c;实现链路层的数据传输和地址封装&#xff08;MA…

马蹄集 三角形坐标

三角形坐标 难度&#xff1a;青铜 ○时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 输入三角形三个顶点A,B,C的坐标(x,y),根据公式计算并输出三 角形面积。 S1/2*X1y2X2y33y1-X1y3-X2y1-x3y2 #include <bits/stdc.h> using namespace std; int main(){double x[4],…