【前端】Vue项目:旅游App-(8)city:标签页Tabs动态数据:网络请求axios与request、数据管理store与pinia、各种封装

2023-11-12

目标

上一篇搭建了搜索框和Tab栏:【前端】Vue项目:旅游App-(7)city:搜索框search和标签页Tabs

本篇目标:

样式不变:
在这里插入图片描述
数据改为动态的:

在这里插入图片描述
数据从服务器获取:
http://123.207.32.32:1888/api/city/allhttp://www.codercba.com:1888/api/city/all

注意将网络请求request和数据管理pinia 封装

过程与代码

安装相关库

本篇要将网络请求到的数据进行处理,要用到pinia

npm install pinia

本篇使用的网络请求库:axios

npm install axios

封装网络请求相关代码

相关参考:coderwhy ts封装axios库 除去冗余代码 可直接使用 - 掘金 (juejin.cn)

service文件夹用来提供封装好的各种服务。我们在其中建立一个文件夹request,表示用来提供网络请求服务。

其中的index.js文件:封装好的axios网络请求。

import axios from "axios";
import { useLoadingStore } from "@/store/modules/loading";
import { baseURL, TIMEOUT } from "./config";
const loadingStore = useLoadingStore();
class HYRequest {
  constructor(baseURL) {
    this.instance = axios.create({
      baseURL,
      timeout: TIMEOUT,
    });
  }

  request(config) {
    loadingStore.changeLoading(true);
    return new Promise((resolve, reject) => {
      this.instance
        .request(config)
        .then((res) => {
          resolve(res.data);
        })
        .catch((err) => {
          console.log("request err:", err);
          reject(err);
        })
        .finally(() => {
          loadingStore.changeLoading(false);
        });
    });
  }

  get(config) {
    return this.request({ ...config, method: "get" });
  }

  post(config) {
    return this.request({ ...config, method: "post" });
  }
}

export default new HYRequest(baseURL);

其中的config.js用来封装网络请求相关的配置:

const baseURL = "http://123.207.32.32:1888/api";
const TIMEOUT = 5000;

export { baseURL, TIMEOUT };

注意,index.js代码中还有一个useLoadingStore的导入。

store中新建modules文件夹,里面的loading.js文件:

import { defineStore } from "pinia";

export const useLoadingStore = defineStore("loadingStore", {
    state: () => {
        return {
            showLoading: false,
        };
    },
    getters: {
        isLoading: (state) => state.showLoading,
    },
    actions: {
        changeLoading(isLoading) {
            this.showLoading = !!isLoading;
        },
        toggleLoading() {
            this.showLoading = isLoading;
        },
    },
});

网络请求数据

我们用封装好的网络请求库来请求数据。

注意:

  • request中的index导出的是一个对象(new HYRequest())
  • HYRequest.get的参数是对象(...config

代码:

import HYRequest from '@/service/request'

function getAllCity() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url:'/city/all'
    }).then(res => {
        console.log(res)
    })
}

getAllCity()

效果:

得到了数据。观察一下数据,可知:获取到的数据是一个对象,里面的data属性是我们页面需要的数据。

data中有两个属性,分别对应两个tab的数据。

在这里插入图片描述

网络请求数据操作封装

请求到数据之后,我们要进行一些思考。

city.vue写的是显示和选择城市的页面,我们在这个页面里写“网络请求数据”的逻辑是否合适?是否利于维护?答案是否定的。

实际上,我们可以将city页面的所有网络请求的操作都写到一个文件里,city.vue只需要在需要数据时调用即可。简而言之,我们需要对city页面的所有网络请求操作进行封装

我们在service文件夹中建立modules文件夹,所有页面的网络请求操作都放在这里。modules中建立city.js,所有city相关的网络请求操作都放在这里。

代码:

// 此文件保存所有city页面的网络请求
import HYRequest from '@/service/request'

export default function getAllCity() {
    // request的index导出的是一个对象
    return HYRequest.get({
        // 参数也是一个对象
        url: '/city/all'
    })
}

city.vue:

import getAllCity from '@/service/modules/city'

getAllCity()

接下来我们再进行一些思考。

我们在city中只需要导入就可以得到数据了。但是,显然city不会只有一次网络请求数据。也就是说,每次网络请求都需要import一次,这样也会让city.vue代码变得复杂。更重要的是,这些import是相似的,我们可以把它们也封装起来。

我们在service文件夹下新建index.js,里面保存所有会被使用的service:导出所有导入的模块。

// 此文件导入并导出所有要使用的service

export * from '@/service/modules/city'

在city.vue中调用:

import { getAllCity } from "@/service";

getAllCity()

效果:

在这里插入图片描述

pinia管理数据并封装

接下来我们再进行一些思考。

我们已经把所有的网络请求都封装了,在vue页面只需要调用网络请求获取数据使用就行了。但是,我们获取到的数据是一个对象,对象中的data才是我们需要的数据。

思考:在vue的页面中进行处理数据的逻辑是否合适?是否利于维护?

答案是否定的。显然,我们既然已经完成了网络请求的封装,自然也会想到要完成数据处理和存储的封装。

预想:我们把请求到的数据和对数据的处理封装到一个文件里,把处理好的数据导出。在vue的页面中只需要直接使用处理好的数据即可。

这里就要用到pinia。

在store的modules中新建city.js,city.vue页面所有的进行网络请求和数据都封装到这里。

// city.vue页面所有的进行网络请求和数据都封装到这里
import { getAllCity } from "@/service";
import { defineStore } from "pinia";

const useCityStore = defineStore('city', {
    state: () => {
        return {
            allCity: {}
        }
    },
    actions: {
        // 调用网络请求
        async fetchAllCity() {
            const res = await getAllCity()
            this.allCity = res.data
        }
    }
})

export default useCityStore

在vue中:

const cityStore=useCityStore()
cityStore.fetchAllCity()
// cityStore是响应式的
const { allCity } = storeToRefs(cityStore)
console.log(allCity)

效果:

在这里插入图片描述

tab栏改为动态数据

tab栏改为动态数据后,若服务器那边的数据发生了改变(如data有了三个属性),我们这里的代码是不用改的。

<van-tabs v-model:active="TabActive">
    <template v-for="(value, key, index) in allCity">
        <van-tab :title="value.title"></van-tab>
    </template>       
</van-tabs>

效果

不变。

在这里插入图片描述

本篇总结

本篇写了很多的封装,这里对它们之间的关系进行总结。

city.vue需要的数据都存在store中。
store会进行网络请求得到数据,网络请求的代码在service。

本项目views文件夹中所有页面需要的数据都在store的modules中,store的modules所有存储的数据来源都是网络请求得到的。所有的views页面的网络请求都存在service的modules中,store只需调用就可以得到数据。

service中的request是封装axios库。

在这里插入图片描述

总代码

修改或新建的文件

在这里插入图片描述

service

存放各种网络请求。

index

把要用的所有service导入并导出。

// 此文件导入并导出所有要使用的service

export * from '@/service/modules/city'

modules的city

封装city页面的所有网络请求。

// 此文件保存所有city页面的网络请求
import HYRequest from '@/service/request'

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


request的config

封装网络请求相关配置。

const baseURL = "http://123.207.32.32:1888/api";
const TIMEOUT = 5000;

export { baseURL, TIMEOUT };

request的index

封装axios。

import axios from "axios";
import { useLoadingStore } from "@/store/modules/loading";
import { baseURL, TIMEOUT } from "./config";
const loadingStore = useLoadingStore();
class HYRequest {
  constructor(baseURL) {
    this.instance = axios.create({
      baseURL,
      timeout: TIMEOUT,
    });
  }

  request(config) {
    loadingStore.changeLoading(true);
    return new Promise((resolve, reject) => {
      this.instance
        .request(config)
        .then((res) => {
          resolve(res.data);
        })
        .catch((err) => {
          console.log("request err:", err);
          reject(err);
        })
        .finally(() => {
          loadingStore.changeLoading(false);
        });
    });
  }

  get(config) {
    return this.request({ ...config, method: "get" });
  }

  post(config) {
    return this.request({ ...config, method: "post" });
  }
}

export default new HYRequest(baseURL);

store

封装pinia。

modules的city

city页面的所有数据。

// city.vue页面所有的进行网络请求和数据都封装到这里
import { getAllCity } from "@/service";
import { defineStore } from "pinia";

const useCityStore = defineStore('city', {
    state: () => {
        return {
            allCity: {}
        }
    },
    actions: {
        // 调用网络请求
        async fetchAllCity() {
            const res = await getAllCity()
            this.allCity = res.data
        }
    }
})

export default useCityStore

modules的loading

封装网络请求需要的模块。

import { defineStore } from "pinia";

export const useLoadingStore = defineStore("loadingStore", {
    state: () => {
        return {
            showLoading: false,
        };
    },
    getters: {
        isLoading: (state) => state.showLoading,
    },
    actions: {
        changeLoading(isLoading) {
            this.showLoading = !!isLoading;
        },
        toggleLoading() {
            this.showLoading = isLoading;
        },
    },
});

city.vue

将tab的数据改为动态的。

<template>
    <div class="city top-page">
        <!-- show-action:显示 “取消”  -->
        <van-search shape="round" v-model="value" show-action placeholder="城市/区域/位置" @search="onSearch"
            @cancel="onCancel" />
        <van-tabs v-model:active="TabActive">
            <template v-for="(value, key, index) in allCity">
                <van-tab :title="value.title"></van-tab>
            </template>
        </van-tabs>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { showToast } from 'vant';
import useCityStore from '@/store/modules/city'
import { storeToRefs } from 'pinia';

const value = ref('');
const TabActive = ref(0);
const onSearch = (val) => showToast(val);
const onCancel = () => {
    showToast('取消');
}

// tabs的数据
const cityStore = useCityStore()
cityStore.fetchAllCity()
// cityStore是响应式的
const { allCity } = storeToRefs(cityStore)

</script>

<style lang="less" scoped>

</style>

参考

Cannot read properties ofundefined(reading‘data‘)
coderwhy ts封装axios库 除去冗余代码 可直接使用 - 掘金 (juejin.cn)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【前端】Vue项目:旅游App-(8)city:标签页Tabs动态数据:网络请求axios与request、数据管理store与pinia、各种封装 的相关文章

  • Vue Cli 3 禁用代码分割 - 无法删除哈希文件

    我有一个vue config js设置效果很好 并取消了默认的代码分割 但它仍然输出一个 CSS 文件 其哈希值与具有好名称的 CSS 文件相同 我可以编写一个脚本来删除它 但我想知道是否有一种方法可以将文件设置为不输出带有哈希的CSS文件
  • Cypress Vue 组件测试从已挂载发出的事件

    我有一个 vue2 组件 它在其安装的生命周期挂钩中发出一个事件 该事件被发出 并且可以由使用该组件的页面处理 但是 我还想测试该事件是否在我的组件测试中发出 该测试使用赛普拉斯组件测试运行程序 这是一个精简版本 组件 TheCompone
  • 使用 Vuex 更新数组中的对象[重复]

    这个问题在这里已经有答案了 如何使用 Vuex 更新数组内的对象 我尝试了这个 但没有成功 const state categories mutations mutationType UPDATE CATEGORY state id cat
  • 类型“CombinedVueInstance>>”上不存在属性“XXX”

    我使用 TypeScript 创建了一个 vue 组件 并且在以下位置收到此错误data and in methods Property xxx does not exist on type CombinedVueInstance
  • 可以在 Vue 模板中渲染 VNode 吗?

    我遇到的情况是 我有一个渲染函数将一些数据传递到作用域槽 作为此数据的一部分 我想包含一些由渲染函数构造的 VNode 这些 VNode 可以选择由作用域插槽使用 无论如何 在模板中编写作用域槽以输出收到的原始 VNode 时是否存在 Vu
  • 如何在 Laravel 中使用 Vue 路由器?

    我使用 laravel9 和 vue3 进行开发 我的问题很简单 但是路径设置不太顺利 当我访问网址时localhost 8080 tasks 此 url 返回 404 未找到 我收到以下类型错误 获取http localhost 8000
  • 超出最大调用堆栈大小 - Vue.js

    我有一个计算方法 可以让我计算产品的总价和折扣值 并希望获得以下值 总计 折扣 cartTotal var total 0 var discount Math round 0 1 this cartTotal 100 100 this ca
  • vue.js keyup, keydown 事件落后一个字符

    我正在使用 keydown keyup 事件 它调用一个 javascript 函数 该函数将输入框的值打印到控制台 以及事件的 currentTarget 字段的值 并且我注意到它晚了一个字符 例如 如果我输入hello进入输入框 我只看
  • vue中有自动更新这段代码的东西吗?

    我在导航器中找到了这个按钮 当用户登录时会显示该按钮 而当用户注销时该按钮就会消失 但现在我需要在按钮删除 出现之前刷新页面 这是我的代码 Button div div class div div
  • Vue.js 严格模式下不允许对一个属性进行多个定义

    再会 我们正在使用 Vuejs Vuex vue router 构建我们的应用程序https github com vuejs vue hackernews 2 0 https github com vuejs vue hackernews
  • 如何预渲染多个Vue应用页面?

    我正在尝试 未成功 在使用 Vue CLI 搭建的同一项目中预渲染多个 Vue 应用程序的 HTML 由于多种原因 我不想使用 Vue Router 或 Nuxt 等 我尝试过使用预渲染 Spa 插件 https github com ch
  • Laravel 5.2 CORS,GET 不适用于预检选项

    可怕的 CORS 错误 跨源请求被阻止 同源策略不允许读取 远程资源位于http localhost mysite api test http localhost mysite api test 原因 CORS 标头 Access Cont
  • Vue 3:“defineProps”引用本地声明的变量

    为什么我收到警告错误消息 defineProps引用本地声明的变量 eslint vue valid define props 当我在 props 中使用自定义验证器时SFC
  • vue.js:观察输入并未在每次按键时触发

    我有一个input正在监视其模型属性 问题是每次按键时不会调用 watch 方法铬 安卓设备 如果我点击输入文本 就会调用它 过去确实有效 但我不知道发生了什么 在Chrome 桌面版可以正常使用 那就是watch for text每次按键
  • 如何将 vue3-openlayers 插件添加到 nuxt

    我有以下 main ts 文件Vue3 https v3 vuejs org import createApp from vue import App from App vue How to do this in nuxt3 import
  • 监听 Vue.js 中的自定义事件

    Vue js 非常适合处理浏览器事件 例如click or mousedown 但根本不适用于自定义事件 这是代码 HTML div style display none div div div div div
  • ExpressJS - 提供通用 Nuxt 应用程序和 AngularJS SPA

    我有一个具有以下结构的博客项目 服务器 用 Node Express 编写 管理员 AngularJS SPA public AngularJS SPA 目前 管理部分和公共部分具有相同的域 但管理部分使用不同的子域 这允许我在 Expre
  • 作为对象访问 vue-i18n 消息

    我想创建一个取决于页面的动态滑块 security signin slide1 Kitten1 slide2 Kitten2 signup slide1 Kitten1 slide2 Kitten2 slide3 Kitten3 问题是我想
  • Vue 2 转换不起作用

    我不知道我的代码哪里出了问题 这应该是一个简单的过渡 当我单击按钮时 消息显示正确 但只是根本没有发生淡入淡出过渡
  • Vue Draggable - 如何仅替换所选项目以防止移动网格上的所有其他项目?

    这是一个要测试的示例 https codesandbox io s j4vn761455 file src App vue 112 116 https codesandbox io s j4vn761455 file src App vue

随机推荐