当前位置: 首页 > 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;如…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...