diff --git a/src/api/hook-demo/use-fetch-select.ts b/src/api/hook-demo/use-fetch-select.ts new file mode 100644 index 0000000..5998e40 --- /dev/null +++ b/src/api/hook-demo/use-fetch-select.ts @@ -0,0 +1,36 @@ +/** 模拟接口响应数据 */ +const SELECT_DATA = { + code: 0, + data: [ + { + label: "苹果", + value: 1 + }, + { + label: "香蕉", + value: 2 + }, + { + label: "橘子", + value: 3, + disabled: true + } + ], + message: "获取 Select 数据成功" +} + +/** 模拟接口 */ +export function getSelectDataApi() { + return new Promise<typeof SELECT_DATA>((resolve, reject) => { + // 模拟接口响应时间 2s + setTimeout(() => { + // 模拟接口调用成功 + if (Math.random() < 0.8) { + resolve(SELECT_DATA) + } else { + // 模拟接口调用出错 + reject(new Error("接口发生错误")) + } + }, 2000) + }) +} diff --git a/src/api/hook-demo/use-fullscreen-loading.ts b/src/api/hook-demo/use-fullscreen-loading.ts new file mode 100644 index 0000000..6f86104 --- /dev/null +++ b/src/api/hook-demo/use-fullscreen-loading.ts @@ -0,0 +1,24 @@ +/** 模拟接口响应数据 */ +const SUCCESS_DATA = { + code: 0, + data: {}, + message: "获取成功" +} + +/** 模拟请求接口成功 */ +export function getSuccessApi() { + return new Promise<typeof SUCCESS_DATA>((resolve) => { + setTimeout(() => { + resolve(SUCCESS_DATA) + }, 1000) + }) +} + +/** 模拟请求接口失败 */ +export function getErrorApi() { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject(new Error("发生错误")) + }, 1000) + }) +} diff --git a/src/api/mock.ts b/src/api/mock.ts deleted file mode 100644 index d16c594..0000000 --- a/src/api/mock.ts +++ /dev/null @@ -1,53 +0,0 @@ -export function getRemoteSelectData() { - return new Promise<any>((resolve, reject) => { - setTimeout(() => { - // 模拟接口调用有概率出错 - if (Math.random() > 0.5) { - resolve({ - code: 0, - data: [ - { - key: 1, - label: "苹果", - value: 1 - }, - { - key: 2, - label: "香蕉", - value: 2 - }, - { - key: 3, - label: "橘子", - value: 3 - } - ], - message: "成功" - }) - } else { - reject(new Error("不小心出错了!")) - } - }, 3000) - }) -} - -export interface IComicsItem { - id: number - name: string -} - -export const getComics = () => { - return new Promise<IComicsItem[]>((resolve) => { - setTimeout(() => { - resolve([...Array(5)].map((_t, index) => ({ id: index, name: `c${index}` }))) - }, 1000) - }) -} - -export const getAnimations = (id: number) => { - return new Promise((_resolve, reject) => { - setTimeout(() => { - reject(new Error(`Sorry, there is an error here. The error id is ${id}`)) - }, 1000) - }) -} diff --git a/src/hooks/useFetchSelect.ts b/src/hooks/useFetchSelect.ts index d3812cf..02c623c 100644 --- a/src/hooks/useFetchSelect.ts +++ b/src/hooks/useFetchSelect.ts @@ -1,51 +1,44 @@ -import { onMounted, ref } from "vue" +import { ref, onMounted } from "vue" -// 定义下拉框接收的数据格式 -export interface SelectOption { - value: string +type OptionValueType = string | number + +/** Select 需要的数据格式 */ +interface ISelectOption { + value: OptionValueType label: string disabled?: boolean - key?: string } -// 定义入参格式 -interface FetchSelectProps { - apiFun: () => Promise<any> +/** 接口响应格式 */ +interface IApiData { + code: number + data: ISelectOption[] + message: string } -export function useFetchSelect(props: FetchSelectProps) { - const { apiFun } = props +/** 入参格式,暂时只需要传递 api 函数即可 */ +interface IFetchSelectProps { + api: () => Promise<IApiData> +} - const options = ref<SelectOption[]>([]) +export function useFetchSelect(props: IFetchSelectProps) { + const { api } = props - const loading = ref(false) + const loading = ref<boolean>(false) + const options = ref<ISelectOption[]>([]) + const value = ref<OptionValueType>("") - const selectedValue = ref("") - - /* 调用接口请求数据 */ + /** 调用接口获取数据 */ const loadData = () => { loading.value = true options.value = [] - return apiFun().then( - (res) => { - loading.value = false + api() + .then((res) => { options.value = res.data - return res.data - }, - (err) => { - // 未知错误,可能是代码抛出的错误,或是网络错误 + }) + .finally(() => { loading.value = false - options.value = [ - { - value: "-1", - label: err.message, - disabled: true - } - ] - // 接着抛出错误 - return Promise.reject(err) - } - ) + }) } onMounted(() => { @@ -53,8 +46,8 @@ export function useFetchSelect(props: FetchSelectProps) { }) return { - options, loading, - selectedValue + options, + value } } diff --git a/src/hooks/useFullscreenLoading.ts b/src/hooks/useFullscreenLoading.ts index 64e14f9..46d810c 100644 --- a/src/hooks/useFullscreenLoading.ts +++ b/src/hooks/useFullscreenLoading.ts @@ -1,53 +1,53 @@ -import { ElLoading, LoadingOptions } from "element-plus" +import { type LoadingOptions, ElLoading } from "element-plus" -const defaultOption = { +const defaultOptions = { lock: true, - text: "加载中...", - background: "rgba(0, 0, 0, 0.7)" + text: "加载中..." } -interface ILoading { +interface ILoadingInstance { close: () => void } /** - * 传入一个方法 fn,在它执行周期内,加上「全屏」loading + * 传入一个函数 fn,在它执行周期内,加上「全屏」loading, * 如果: - * 1. fn 是同步方法,结束后隐藏 loading - * 2. 如果是异步方法,resolve 后隐藏 loading + * 1. fn 如果是同步函数,执行结束后隐藏 loading + * 2. fn 如果是 Promise,resolve 或 reject 后隐藏 loading * 3. 报错后隐藏 loading 并抛出错误 * @param {*} fn 函数 - * @param options + * @param options LoadingOptions * @returns Function 一个新的函数,去执行它吧 */ -export const useFullscreenLoading = <T>( - fn: (...args: any[]) => T, +export function useFullscreenLoading<T>( + fn: (...args: any[]) => T | Promise<T>, options: LoadingOptions = {} -): ((...args: any[]) => Promise<T>) => { - let loading: ILoading | undefined +): (...args: any[]) => Promise<T> { + let loadingInstance: ILoadingInstance const showLoading = (options: LoadingOptions) => { - loading = ElLoading.service(options) + loadingInstance = ElLoading.service(options) } - const hideLoading = () => { - loading && loading.close() + loadingInstance && loadingInstance.close() } - const _options = { ...defaultOption, ...options } - const newFn = (...args: any[]) => { + const _options = { ...defaultOptions, ...options } + return (...args: any[]) => { try { showLoading(_options) const result = fn(...args) const isPromise = result instanceof Promise + // 同步函数 if (!isPromise) { hideLoading() return Promise.resolve(result) } + // Promise return result - .then((res: any) => { + .then((res) => { hideLoading() return res }) - .catch((err: Error) => { + .catch((err) => { hideLoading() throw err }) @@ -56,5 +56,4 @@ export const useFullscreenLoading = <T>( throw err } } - return newFn } diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts index 9edd1f3..d860884 100644 --- a/src/hooks/usePagination.ts +++ b/src/hooks/usePagination.ts @@ -25,7 +25,7 @@ const defaultPaginationData: IDefaultPaginationData = { layout: "total, sizes, prev, pager, next, jumper" } -export const usePagination = (_paginationData: IPaginationData = {}) => { +export function usePagination(_paginationData: IPaginationData = {}) { /** 合并分页参数 */ const paginationData = reactive(Object.assign({ ...defaultPaginationData }, _paginationData)) diff --git a/src/router/index.ts b/src/router/index.ts index ba71697..e5d4c73 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -190,19 +190,19 @@ export const constantRoutes: RouteRecordRaw[] = [ ] }, { - path: "/hooks", + path: "/hook-demo", component: Layout, - redirect: "/hooks/use-fetch-select", - name: "Hooks", + redirect: "/hook-demo/use-fetch-select", + name: "HookDemo", meta: { - title: "hooks", + title: "hook 示例", elIcon: "Menu", alwaysShow: true }, children: [ { path: "use-fetch-select", - component: () => import("@/views/hooks/use-fetch-select.vue"), + component: () => import("@/views/hook-demo/use-fetch-select.vue"), name: "UseFetchSelect", meta: { title: "useFetchSelect" @@ -210,7 +210,7 @@ export const constantRoutes: RouteRecordRaw[] = [ }, { path: "use-fullscreen-loading", - component: () => import("@/views/hooks/use-fullscreen-loading.vue"), + component: () => import("@/views/hook-demo/use-fullscreen-loading.vue"), name: "UseFullscreenLoading", meta: { title: "useFullscreenLoading" diff --git a/src/views/hook-demo/use-fetch-select.vue b/src/views/hook-demo/use-fetch-select.vue new file mode 100644 index 0000000..ecf144c --- /dev/null +++ b/src/views/hook-demo/use-fetch-select.vue @@ -0,0 +1,20 @@ +<script lang="ts" setup> +import { useFetchSelect } from "@/hooks/useFetchSelect" +import { getSelectDataApi } from "@/api/hook-demo/use-fetch-select" + +const { loading, options, value } = useFetchSelect({ + api: getSelectDataApi +}) +</script> + +<template> + <div class="app-container"> + <h4>该示例是演示:通过 hook 自动调用 api 后拿到 Select 组件需要的数据并传递给 Select 组件</h4> + <h5>Select 示例</h5> + <el-select :loading="loading" v-model="value" filterable> + <el-option v-for="(item, index) in options" v-bind="item" :key="index" placeholder="请选择" /> + </el-select> + <h5>Select V2 示例(如果数据量过多,可以选择该组件)</h5> + <el-select-v2 :loading="loading" v-model="value" :options="options" filterable placeholder="请选择" /> + </div> +</template> diff --git a/src/views/hook-demo/use-fullscreen-loading.vue b/src/views/hook-demo/use-fullscreen-loading.vue new file mode 100644 index 0000000..460444a --- /dev/null +++ b/src/views/hook-demo/use-fullscreen-loading.vue @@ -0,0 +1,44 @@ +<script lang="ts" setup> +import { useFullscreenLoading } from "@/hooks/useFullscreenLoading" +import { getSuccessApi, getErrorApi } from "@/api/hook-demo/use-fullscreen-loading" +import { ElMessage } from "element-plus" + +const svg = ` + <path class="path" d=" + M 30 15 + L 28 17 + M 25.61 25.61 + A 15 15, 0, 0, 1, 15 30 + A 15 15, 0, 1, 1, 27.99 7.5 + L 15 15 + " style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/> +` + +const options = { + text: "即将发生错误...", + background: "#F56C6C20", + svg, + svgViewBox: "-10, -10, 50, 50" +} + +const querySuccess = async () => { + const res = await useFullscreenLoading(getSuccessApi)() + ElMessage.success(res.message) +} + +const queryError = async () => { + try { + await useFullscreenLoading(getErrorApi, options)() + } catch (err: any) { + ElMessage.error(err.message) + } +} +</script> + +<template> + <div class="app-container"> + <h4>该示例是演示:通过将要执行的函数传递给 hook,让 hook 自动开启全屏 loading,函数执行结束后自动关闭 loading</h4> + <el-button @click="querySuccess">查询成功</el-button> + <el-button @click="queryError">查询失败</el-button> + </div> +</template> diff --git a/src/views/hooks/use-fetch-select.vue b/src/views/hooks/use-fetch-select.vue deleted file mode 100644 index f8f9467..0000000 --- a/src/views/hooks/use-fetch-select.vue +++ /dev/null @@ -1,18 +0,0 @@ -<script setup name="Select" lang="ts"> -import { getRemoteSelectData } from "@/api/mock" -import { useFetchSelect } from "@/hooks/useFetchSelect" - -const { loading, options, selectedValue } = useFetchSelect({ - apiFun: getRemoteSelectData -}) -</script> - -<template> - <div class="app-container"> - <el-select :loading="loading" v-model="selectedValue"> - <el-option v-for="(item, index) in options" v-bind="item" :key="index" /> - </el-select> - <span class="m-x">Select V2 示例</span> - <el-select-v2 :loading="loading" v-model="selectedValue" :options="options" /> - </div> -</template> diff --git a/src/views/hooks/use-fullscreen-loading.vue b/src/views/hooks/use-fullscreen-loading.vue deleted file mode 100644 index a8958aa..0000000 --- a/src/views/hooks/use-fullscreen-loading.vue +++ /dev/null @@ -1,41 +0,0 @@ -<template> - <div class="app-container"> - <el-button @click="querySuccess">查询成功</el-button> - <el-button @click="queryFailed">查询失败</el-button> - </div> -</template> - -<script lang="ts" setup> -import { getComics, getAnimations, type IComicsItem } from "@/api/mock" -import { useFullscreenLoading } from "@/hooks/useFullscreenLoading" -import { ElMessage } from "element-plus" - -const querySuccess = async () => { - const comics = await useFullscreenLoading(getComics)() - ElMessage.success("Successfully get comics: " + comics.map((t: IComicsItem) => t.name).join()) -} - -const svg = ` - <path class="path" d=" - M 30 15 - L 28 17 - M 25.61 25.61 - A 15 15, 0, 0, 1, 15 30 - A 15 15, 0, 1, 1, 27.99 7.5 - L 15 15 - " style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/> -` - -const queryFailed = async () => { - try { - await useFullscreenLoading(getAnimations, { - text: "自定义加载文字", - background: "rgba(255, 214, 210, 0.7)", - svg, - svgViewBox: "-10, -10, 50, 50" - })(233) - } catch (err: any) { - ElMessage.error(err.message) - } -} -</script>