這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
在開發管理后臺程序中,一定會遇到不少了增刪改查頁面,而這些頁面的邏輯大多都是相同的,如獲取串列資料,分頁,篩選功能這些基本功能,而不同的是呈現出來的資料項,還有一些操作按鈕,

對于剛開始只有 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的值),
- 執行流程
- 設定加載狀態
- 呼叫外部傳入的函式,將獲取到的資料賦值到
list和total中 - 關閉加載態
這里使用了 async/await 語法,假設請求出錯、解構出錯情況會走 catch 代碼塊,再關閉加載態
這里需要注意,傳入的 listRequestFn 函式接收的引數數量和型別是否正常對應上 請根據實際情況進行調整
export default function useList<ItemType extends Object>(
listRequestFn: Function
) {
// 忽略其他代碼
// 資料
const list = ref<ItemType[]>([]);
// 過濾資料
// 獲取串列資料
const loadData = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/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 = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/false;
}
};
}
別忘了,還有切換分頁要處理
使用 watch 函式監聽資料,當curPage,pageSize的值發生改變時呼叫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 = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/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 = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/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 = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/{} 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
) {
// ...
}
在沒有傳遞鉤子的情況霞,推薦設定默認的失敗時資訊顯示
優化loadData,exportFile函式
基于 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 = https://www.cnblogs.com/smileZAZ/archive/2023/03/04/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 >
<el-collapse-item title="篩選條件" name="1">
<el-form label-position="left" label- :model="filterOption">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="用戶名">
<el-input
v-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-picker
v-model="filterOption.timeRange"
type="daterange"
unlink-panels
range-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 >
<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="https://www.cnblogs.com/smileZAZ/archive/2023/03/04/list" border style="width: 100%">
<el-table-column label="用戶名" min->
<template #default="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="手機號碼" min->
<template #default="scope">
{{ scope.row.mobile || "未系結手機號碼" }}
</template>
</el-table-column>
<el-table-column label="郵箱地址" min->
<template #default="scope">
{{ scope.row.email || "未系結郵箱地址" }}
</template>
</el-table-column>
<el-table-column prop="createAt" label="注冊時間" min- />
<el-table-column 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" >
<el-pagination
v-model:current-page="curPage"
v-model:page-size="pageSize"
background
layout="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
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545814.html
標籤:其他

