当前位置: 首页 > news >正文

记录--在Vue3这样子写页面更快更高效

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

在开发管理后台过程中,一定会遇到不少了增删改查页面,而这些页面的逻辑大多都是相同的,如获取列表数据,分页,筛选功能这些基本功能。而不同的是呈现出来的数据项。还有一些操作按钮。

对于刚开始只有 1,2 个页面的时候大多数开发者可能会直接将之前的页面代码再拷贝多一份出来,而随着项目的推进类似页面数量可能会越来越多,这直接导致项目代码耦合度越来越高。

这也是为什么在项目中一些可复用的函数或组件要抽离出来的主要原因之一

下面,我们封装一个通用的useList,适配大多数增删改查的列表页面,让你更快更高效的完成任务,准点下班 ~

前置知识

  • Vue
  • Vue Composition Api

封装

我们需要将一些通用的参数和函数抽离出来,封装成一个通用hook,后续在其他页面复用相同功能更加简单方便。

定义列表页面必不可少的分页数据

export default function useList() {// 加载态const loading = ref(false);// 当前页const curPage = ref(1);// 总数量const total = ref(0);// 分页大小const pageSize = ref(10);
}

如何获取列表数据

思考一番,让useList函数接收一个listRequestFn参数,用于请求列表中的数据。

定义一个list变量,用于存放网络请求回来的数据内容,由于在内部无法直接确定列表数据类型,通过泛型的方式让外部提供列表数据类型。

export default function useList<ItemType extends Object>(listRequestFn: Function
) {// 忽略其他代码const list = ref<ItemType[]>([]);
}

useList中创建一个loadData函数,用于调用获取数据函数,该函数接收一个参数用于获取指定页数的数据(可选,默认为curPage的值)。

  • 执行流程
  1. 设置加载状态
  2. 调用外部传入的函数,将获取到的数据赋值到listtotal
  3. 关闭加载态

这里使用了 async/await 语法,假设请求出错、解构出错情况会走 catch 代码块,再关闭加载态

这里需要注意,传入的 listRequestFn 函数接收的参数数量和类型是否正常对应上 请根据实际情况进行调整

export default function useList<ItemType extends Object>(listRequestFn: Function
) {// 忽略其他代码// 数据const list = ref<ItemType[]>([]);// 过滤数据// 获取列表数据const loadData = async (page = curPage.value) => {// 设置加载中loading.value = true;try {const {data,meta: { total: count },} = await listRequestFn(pageSize.value, page);list.value = data;total.value = count;} catch (error) {console.log("请求出错了", "error");} finally {// 关闭加载中loading.value = false;}};
}

别忘了,还有切换分页要处理

使用 watch 函数监听数据,当curPagepageSize的值发生改变时调用loadData函数获取新的数据。

export default function useList<ItemType extends Object>(listRequestFn: Function
) {// 忽略其他代码// 监听分页数据改变watch([curPage, pageSize], () => {loadData(curPage.value);});
}

现在实现了基本的列表数据获取

实现数据筛选器

在庞大的数据列表中,数据筛选是必不可少的功能

通常,我会将筛选条件字段定义在一个ref中,在请求时将ref丢到请求函数即可。

在 useList 函数中,第二个参数接收一个filterOption对象,对应列表中的筛选条件字段。

调整一下loadData函数,在请求函数中传入filterOption对象即可

注意,传入的 listRequestFn 函数接收的参数数量和类型是否正常对应上 请根据实际情况进行调整

export default function useList<ItemType extends Object,FilterOption extends Object
>(listRequestFn: Function, filterOption: Ref<Object>) {const loadData = async (page = curPage.value) => {// 设置加载中loading.value = true;try {const {data,meta: { total: count },} = await listRequestFn(pageSize.value, page, filterOption.value);list.value = data;total.value = count;} catch (error) {console.log("请求出错了", "error");} finally {// 关闭加载中loading.value = false;}};
}

注意,这里 filterOption 参数类型需要的是 ref 类型,否则会丢失响应式 无法正常工作

清空筛选器字段

在页面中,有一个重置的按钮,用于清空筛选条件。这个重复的动作可以交给 reset 函数处理。

通过使用 Reflect 将所有值设定为undefined,再重新请求一次数据。

什么是 Reflect?看看这一篇文章Reflect 映射对象

export default function useList<ItemType extends Object,FilterOption extends Object
>(listRequestFn: Function, filterOption: Ref<Object>) {const reset = () => {if (!filterOption.value) return;const keys = Reflect.ownKeys(filterOption.value);filterOption.value = {} as FilterOption;keys.forEach((key) => {Reflect.set(filterOption.value!, key, undefined);});loadData();};
}

导出功能

除了对数据的查看,有些界面还需要有导出数据功能(例如导出 csv,excel 文件),我们也把导出功能写到useList

通常,导出功能是调用后端提供的导出Api获取一个文件下载地址,和loadData函数类似,从外部获取exportRequestFn函数来调用Api

在函数中,新增一个exportFile函数调用它。

export default function useList<ItemType extends Object,FilterOption extends Object
>(listRequestFn: Function,filterOption: Ref<Object>,exportRequestFn?: Function
) {// 忽略其他代码const exportFile = async () => {if (!exportRequestFn) {throw new Error("当前没有提供exportRequestFn函数");}if (typeof exportRequestFn !== "function") {throw new Error("exportRequestFn必须是一个函数");}try {const {data: { link },} = await exportRequestFn(filterOption.value);window.open(link);} catch (error) {console.log("导出失败", "error");}};
}

注意,传入的 exportRequestFn 函数接收的参数数量和类型是否正常对应上 请根据实际情况进行调整

优化

现在,整个useList已经满足了页面上的需求了,拥有了获取数据,筛选数据,导出数据,分页功能

还有一些细节方面,在上面所有代码中的try..catch中的catch代码片段并没有做任何的处理,只是简单的console.log一下

提供钩子

useList新增一个 Options 对象参数,用于函数成功、失败时执行指定钩子函数与输出消息内容。

定义 Options 类型

export interface MessageType {GET_DATA_IF_FAILED?: string;GET_DATA_IF_SUCCEED?: string;EXPORT_DATA_IF_FAILED?: string;EXPORT_DATA_IF_SUCCEED?: string;
}
export interface OptionsType {requestError?: () => void;requestSuccess?: () => void;message: MessageType;
}export default function useList<ItemType extends Object,FilterOption extends Object
>(listRequestFn: Function,filterOption: Ref<Object>,exportRequestFn?: Function,options? :OptionsType
) {// ...
}

设置Options默认值

const DEFAULT_MESSAGE = {GET_DATA_IF_FAILED: "获取列表数据失败",EXPORT_DATA_IF_FAILED: "导出数据失败",
};const DEFAULT_OPTIONS: OptionsType = {message: DEFAULT_MESSAGE,
};export default function useList<ItemType extends Object,FilterOption extends Object
>(listRequestFn: Function,filterOption: Ref<Object>,exportRequestFn?: Function,options = DEFAULT_OPTIONS
) {// ...
}

在没有传递钩子的情况霞,推荐设置默认的失败时信息显示

优化loadDataexportFile函数

基于 elementui 封装 message 方法

import { ElMessage, MessageOptions } from "element-plus";export function message(message: string, option?: MessageOptions) {ElMessage({ message, ...option });
}
export function warningMessage(message: string, option?: MessageOptions) {ElMessage({ message, ...option, type: "warning" });
}
export function errorMessage(message: string, option?: MessageOptions) {ElMessage({ message, ...option, type: "error" });
}
export function infoMessage(message: string, option?: MessageOptions) {ElMessage({ message, ...option, type: "info" });
}

loadData 函数

const loadData = async (page = curPage.value) => {loading.value = true;try {const {data,meta: { total: count },} = await listRequestFn(pageSize.value, page, filterOption.value);list.value = data;total.value = count;// 执行成功钩子options?.message?.GET_DATA_IF_SUCCEED &&message(options.message.GET_DATA_IF_SUCCEED);options?.requestSuccess?.();} catch (error) {options?.message?.GET_DATA_IF_FAILED &&errorMessage(options.message.GET_DATA_IF_FAILED);// 执行失败钩子options?.requestError?.();} finally {loading.value = false;}
};

exportFile 函数

const exportFile = async () => {if (!exportRequestFn) {throw new Error("当前没有提供exportRequestFn函数");}if (typeof exportRequestFn !== "function") {throw new Error("exportRequestFn必须是一个函数");}try {const {data: { link },} = await exportRequestFn(filterOption.value);window.open(link);// 显示信息options?.message?.EXPORT_DATA_IF_SUCCEED &&message(options.message.EXPORT_DATA_IF_SUCCEED);// 执行成功钩子options?.exportSuccess?.();} catch (error) {// 显示信息options?.message?.EXPORT_DATA_IF_FAILED &&errorMessage(options.message.EXPORT_DATA_IF_FAILED);// 执行失败钩子options?.exportError?.();}
};

useList 使用方法

<template><el-collapse class="mb-6"><el-collapse-item title="筛选条件" name="1"><el-form label-position="left" label-width="90px" :model="filterOption"><el-row :gutter="20"><el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8"><el-form-item label="用户名"><el-inputv-model="filterOption.name"placeholder="筛选指定签名名称"/></el-form-item></el-col><el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8"><el-form-item label="注册时间"><el-date-pickerv-model="filterOption.timeRange"type="daterange"unlink-panelsrange-separator="到"start-placeholder="开始时间"end-placeholder="结束时间"format="YYYY-MM-DD HH:mm"value-format="YYYY-MM-DD HH:mm"/></el-form-item></el-col><el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24"><el-row class="flex mt-4"><el-button type="primary" @click="filter">筛选</el-button><el-button type="primary" @click="reset">重置</el-button></el-row></el-col></el-row></el-form></el-collapse-item></el-collapse><el-table v-loading="loading" :data="list" border style="width: 100%"><el-table-column label="用户名" min-width="110px"><template #default="scope">{{ scope.row.name }}</template></el-table-column><el-table-column label="手机号码" min-width="130px"><template #default="scope">{{ scope.row.mobile || "未绑定手机号码" }}</template></el-table-column><el-table-column label="邮箱地址" min-width="130px"><template #default="scope">{{ scope.row.email || "未绑定邮箱地址" }}</template></el-table-column><el-table-column prop="createAt" label="注册时间" min-width="220px" /><el-table-column width="200px" fixed="right" label="操作"><template #default="scope"><el-button type="primary" link @click="detail(scope.row)">详情</el-button></template></el-table-column></el-table><div v-if="total > 0" class="flex justify-end mt-4"><el-paginationv-model:current-page="curPage"v-model:page-size="pageSize"backgroundlayout="sizes, prev, pager, next":total="total":page-sizes="[10, 30, 50]"/></div>
</template>
<script setup lang="ts">
import { UserInfoApi } from "@/network/api/User";
import useList from "@/lib/hooks/useList/index";
const filterOption = ref<UserInfoApi.FilterOptionType>({});
const {list,loading,reset,filter,curPage,pageSize,reload,total,loadData,
} = useList<UserInfoApi.UserInfo[], UserInfoApi.FilterOptionType>(UserInfoApi.list,filterOption
);
</script>

本文useList的完整代码在 github.com/QC2168/snip…

本文转载于:

https://juejin.cn/post/7172889961446768670

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

相关文章:

记录--在Vue3这样子写页面更快更高效

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在开发管理后台过程中&#xff0c;一定会遇到不少了增删改查页面&#xff0c;而这些页面的逻辑大多都是相同的&#xff0c;如获取列表数据&#xff0c;分页&#xff0c;筛选功能这些基本功能。而…...

【程序设计与算法(三)】测验和作业题部分答案汇总(面向对象篇)

题目来源&#xff1a;程序设计与算法&#xff08;三&#xff09;测验和作业题汇总 文章目录001:简单的swap002:难一点的swap003:好怪异的返回值004:神秘的数组初始化005:编程填空&#xff1a;学生信息处理程序006:奇怪的类复制007:返回什么才好呢008:超简单的复数类009:哪来的输…...

LeetCode 349. 两个数组的交集和 692. 前K个高频单词

两个数组的交集 难度 简单 题目链接 这道题的难度不大&#xff0c;我们可以把数组里的数据存到set里面。这样就完成了排序和去重&#xff0c;然后我们再把一个set里面的数据和另外一个set数据进行比较。如果相同就插入到数组里。 代码如下&#xff1a; 但是这个算法的时间复…...

SpringCloud的五大组件功能

SpringCloud的五大组件 EurekaRibbonHystrixZuulConfig 一、Eureka 作用是实现服务治理&#xff0c;即服务注册与发现。 Eureka服务器相当于一个中介&#xff0c;负责管理、记录服务提供者的信息。服务调用者不需要自己寻找服务 &#xff0c;而是把需求告诉Eureka &#x…...

剑指 Offer II 016. 不含重复字符的最长子字符串

题目链接 剑指 Offer II 016. 不含重复字符的最长子字符串 mid 题目描述 给定一个字符串 s&#xff0c;请你找出其中不含有重复字符的 最长连续子字符串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子字符串是 “abc”&#xff0c;所以其长度…...

HBase读取流程详解

读流程从头到尾可以分为如下4个步骤&#xff1a;Client-Server读取交互逻辑&#xff0c;Server端Scan框架体系&#xff0c;过滤淘汰不符合查询条件的HFile&#xff0c;从HFile中读取待查找Key。其中Client-Server交互逻辑主要介绍HBase客户端在整个scan请求的过程中是如何与服务…...

Redis学习(一):NoSQL概述

为什么要使用Nosql 现在是大数据时代&#xff0c;过大的数据一般的数据库无法进行分析处理了。 单机MySQL的年代 90年代&#xff0c;一个基本的网站访问量一般不会太大&#xff0c;单个数据库完全足够&#xff01; 那个时候&#xff0c;更多的去使用静态网站&#xff0c;服务器…...

ESP32设备驱动-MCP23017并行IO扩展驱动

MCP23017并行IO扩展驱动 1、MCP23017介绍 MCP23017是一个用于 I2C 总线应用的 16 位通用并行 I/O 端口扩展器。 16 位 I/O 端口在功能上由两个 8 位端口(PORTA 和 PORTB)组成。 MCP23017 可配置为在 8 位或 16 位模式下工作。 其引脚排列如下: MCP23017 在 3.3v 下工作正常…...

RabbitMQ简介

0. 学习目标 能够说出什么是消息中间件能够安装RabbitMQ能够编写RabbitMQ的入门程序能够说出RabbitMQ的5种模式特征能够使用Spring整合RabbitMQ 1. 消息中间件概述 1.1. 什么是消息中间件 MQ全称为Message Queue&#xff0c;消息队列是应用程序和应用程序之间的通信方法。是…...

【项目设计】高并发内存池(五)[释放内存流程及调通]

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…...

Git标签与版本发布

1. 什么是git标签 标签&#xff0c;就类似我们阅读时的书签&#xff0c;可以很轻易找到自己阅读到了哪里。 对于git来说&#xff0c;在使用git对项目进行版本管理的时候&#xff0c;当我们的项目开发到一定的阶段&#xff0c;需要发布一个版本。这时&#xff0c;我们就可以对…...

Python面向对象编程

文章目录1 作用域1.1 局部作用域2 类成员权限3 是否继承新式类4 多重继承5 虚拟子类6 内省【在运行时确定对象类型的能力】7 函数参数8 生成器1 作用域 1.1 局部作用域 1&#xff0c;当局部变量遮盖全局变量&#xff0c;使用globals()[变量名]来使用全局变量&#xff1b;2&am…...

【什么情况会导致 MySQL 索引失效?】

MySQL索引失效可能有多种原因&#xff0c;下面列举一些常见的情况&#xff1a; 数据库表数据量太小&#xff1a; 如果表的数据量非常小&#xff0c;则MySQL可能不会使用索引&#xff0c;因为它认为全表扫描的代价更小。 索引列上进行了函数操作&#xff1a; 如果在索引列上…...

Java核心知识点整理之小碎片--每天一点点(坚持呀)--自问自答系列版本

1.int和Integer Integer是int的包装类&#xff1b;int是基本数据类型。 Integer变量必须实例化后才能使用&#xff1b;int变量不需要。 Integer实际是对象的引用&#xff0c;当new一个Integer时&#xff0c;实际上是生成一个指针指向此对象&#xff1b;而int则是直接存储数据值…...

js中new Map()的使用方法

1.map的方法及属性Map对象存有键值对&#xff0c;其中的键可以是任何数据类型。Map对象记得键的原始插入顺序。Map对象具有表示映射大小的属性。1.1 基本的Map() 方法MethodDescriptionnew Map()创建新的 Map 对象。set()为 Map 对象中的键设置值。get()获取 Map 对象中键的值。…...

synchronized从入门到踹门

synchronized是什么synchronized是Java关键字&#xff0c;为了维护高并发是出现的原子性问题。技术是把双刃剑&#xff0c;多线程并发给我带来了前所未有的速率&#xff0c;然而在享受快速编程的过程&#xff0c;也给我们带来了原子性问题。如下&#xff1a;public class Main …...

ubuntu-8-安装nfs服务共享目录

Ubuntu最新版本(Ubuntu22.04LTS)安装nfs服务器及使用教程 ubuntu16.04挂载_如何在Ubuntu 20.04上设置NFS挂载 Ubuntu 20.04 设置时区、配置NTP同步 timesyncd 代替 ntpd 服务器 10.0.2.11 客户端 10.0.2.121 NFS简介 (1)什么是NFS NFS就是Network File System的缩写&#xf…...

算法练习(特辑)设计算法的常用思想

1、递推法 递推的思想是把一个复杂的庞大的计算过程转换为简单过程的多次重复&#xff0c;每一次推导的结果作为下一次推导的开始。 2、递归法 递归算法实际上是把问题转化成规模更小的同类子问题&#xff0c;先解决子问题&#xff0c;再通过相同的求解过程逐步解决更高层次…...

哈希->模拟实现+位图应用

致前行路上的人&#xff1a; 要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 目录 1. unordered系列关联式容器 1.1 unordered_map 1.1.1概念介绍&#xff1a; 1.1.2 unordered_map的接口说明 1.2unordered_set 1.3常见面试题oj…...

苹果手机想要传输数据到电脑怎么传输呢?

苹果手机想要传输数据到电脑怎么传输呢&#xff1f;尤其是传输数据到Windows系统&#xff0c;可能需要使用一些传输软件&#xff0c;那么常用的传输软件有哪些呢&#xff1f;下文将为大家推荐几款常用的苹果手机数据传输常用工具。近期苹果发布了iPhone14系列手机&#xff0c;如…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...

spring boot使用HttpServletResponse实现sse后端流式输出消息

1.以前只是看过SSE的相关文章&#xff0c;没有具体实践&#xff0c;这次接入AI大模型使用到了流式输出&#xff0c;涉及到给前端流式返回&#xff0c;所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...