From cdf5bc1591c763d0f04150238ab001854e6cdad2 Mon Sep 17 00:00:00 2001 From: qiang Date: Sat, 15 Jul 2023 14:52:47 +0800 Subject: [PATCH] refactor: request (#38) --- .eslintrc-auto-import.json | 5 +- auto-imports.d.ts | 11 +- src/composables/cates.ts | 4 +- src/composables/crud.ts | 273 ++----------------------------------- src/composables/detail.ts | 48 +++++++ src/composables/form.ts | 92 +++++++++++++ src/composables/list.ts | 89 ++++++++++++ src/composables/remove.ts | 63 +++++++++ src/composables/request.ts | 20 ++- 9 files changed, 328 insertions(+), 277 deletions(-) create mode 100644 src/composables/detail.ts create mode 100644 src/composables/form.ts create mode 100644 src/composables/list.ts create mode 100644 src/composables/remove.ts diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 8fec8de..dcf1afa 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -143,6 +143,7 @@ "useArrayFilter": true, "useArrayFind": true, "useArrayFindIndex": true, + "useArrayFindLast": true, "useArrayJoin": true, "useArrayMap": true, "useArrayReduce": true, @@ -177,7 +178,6 @@ "useDebounceFn": true, "useDebouncedRefHistory": true, "useDelete": true, - "useDeleteItem": true, "useDetail": true, "useDeviceMotion": true, "useDeviceOrientation": true, @@ -241,6 +241,7 @@ "useParallax": true, "usePermission": true, "usePointer": true, + "usePointerLock": true, "usePointerSwipe": true, "usePost": true, "usePreferredColorScheme": true, @@ -248,9 +249,11 @@ "usePreferredDark": true, "usePreferredLanguages": true, "usePreferredReducedMotion": true, + "usePrevious": true, "usePut": true, "useRafFn": true, "useRefHistory": true, + "useRemove": true, "useResizeObserver": true, "useRoute": true, "useRouter": true, diff --git a/auto-imports.d.ts b/auto-imports.d.ts index b8b5d7a..ea09e10 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -144,6 +144,7 @@ declare global { const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] + const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] @@ -178,8 +179,7 @@ declare global { const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] const useDelete: typeof import('./src/composables/request')['useDelete'] - const useDeleteItem: typeof import('./src/composables/crud')['useDeleteItem'] - const useDetail: typeof import('./src/composables/crud')['useDetail'] + const useDetail: typeof import('./src/composables/detail')['useDetail'] const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] @@ -203,7 +203,7 @@ declare global { const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] const useFocus: typeof import('@vueuse/core')['useFocus'] const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] - const useForm: typeof import('./src/composables/crud')['useForm'] + const useForm: typeof import('./src/composables/form')['useForm'] const useFps: typeof import('@vueuse/core')['useFps'] const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] const useGamepad: typeof import('@vueuse/core')['useGamepad'] @@ -219,7 +219,7 @@ declare global { const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] const useLink: typeof import('vue-router')['useLink'] - const useList: typeof import('./src/composables/crud')['useList'] + const useList: typeof import('./src/composables/list')['useList'] const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] @@ -242,6 +242,7 @@ declare global { const useParallax: typeof import('@vueuse/core')['useParallax'] const usePermission: typeof import('@vueuse/core')['usePermission'] const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] const usePost: typeof import('./src/composables/request')['usePost'] const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] @@ -249,9 +250,11 @@ declare global { const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] const usePut: typeof import('./src/composables/request')['usePut'] const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useRemove: typeof import('./src/composables/remove')['useRemove'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] diff --git a/src/composables/cates.ts b/src/composables/cates.ts index ebb9c9c..506747a 100644 --- a/src/composables/cates.ts +++ b/src/composables/cates.ts @@ -8,7 +8,9 @@ export function useCatesList() { const list = useStorage('CatesList', []) const { data, execute } = useGet<{ list: CatesItem[] }>(Api.cates) - !list.value?.length && loadList() + tryOnMounted(() => { + !list.value?.length && loadList() + }) async function loadList() { await execute() diff --git a/src/composables/crud.ts b/src/composables/crud.ts index 1d82f99..f02d90a 100644 --- a/src/composables/crud.ts +++ b/src/composables/crud.ts @@ -1,273 +1,16 @@ import { isFunction } from '@vueuse/core' -import type { Ref } from 'vue' import type { MaybeRef } from '@vueuse/core' import type { ICrudSubmit, ICrudBeforeOpen, - IFormSubmit, StringObject, - UnknownObject, } from 'element-pro-components' -import type { PagesData } from '../types/index' - -interface CommonConfig { - url: MaybeRef - immediate?: MaybeRef -} - -export type ReqFormType = 'post' | 'put' - -export interface UseFormConfig extends Omit { - showTip?: MaybeRef - type?: MaybeRef - transform?: (form: T) => T - afterSubmit?: (res: K | null) => void -} - -export interface UseFormReturn { - isFetching: Ref - form: Ref - submit: IFormSubmit - submitForm: (type?: ReqFormType) => Promise> -} - -/** - * 封装表单提交 - * @param config.url 请求地址 - * @param config.showTip 显示提示,默认: `true` - * @param config.type 提交请求方式 post | put,默认: `post` - * @param config.transform 转换表单 - * @param config.afterSubmit 提交表单后执行的回调函数 - */ -export function useForm
({ - url, - transform, - showTip = true, - type = 'post', - afterSubmit, -}: UseFormConfig): UseFormReturn { - const isFetching = ref(false) - const form = ref({}) as Ref - const payload = ref({}) as Ref - const _url = computed(() => { - return replaceId(unref(url), (payload.value as { id?: string }).id) - }) - const postForm = usePost(url, payload) - const putForm = usePut(_url, payload) - const submit: IFormSubmit = async (done, isValid) => { - if (isValid) { - const res = await submitForm() - - if (res.value) { - unref(showTip) && appMessage('success', '提交成功!') - } else if (res.value !== null) { - unref(showTip) && appMessage('warning', '提交失败!') - } - isFunction(afterSubmit) && afterSubmit(res.value) - } - done() - } - - async function submitForm(reqType = type) { - setPayload() - isFetching.value = true - if (unref(reqType) === 'post') { - await postForm.execute() - isFetching.value = false - return postForm.data - } else { - await putForm.execute() - isFetching.value = false - return putForm.data - } - } - - function setPayload() { - payload.value = isFunction(transform) ? transform(unref(form)) : form.value - } - - return { - isFetching, - form, - submit, - submitForm, - } -} - -export interface UseDetailReturn { - isFetching: Ref - detailId: Ref - detail: Ref - loadDetail: () => Promise -} - -/** - * 封装获取详情 - * @param config.url 请求地址 - * @param config.immediate 是否在修改ID时加载,默认: `true` - */ -export function useDetail({ - url, - immediate = true, -}: CommonConfig): UseDetailReturn { - const detailId = ref(undefined) - const _url = computed(() => replaceId(unref(url), detailId.value)) - const { isFetching, data, execute } = useGet(_url) - - unref(immediate) && - watch(detailId, (val) => { - val !== undefined && loadDetail() - }) - - async function loadDetail() { - if (isFetching.value) return - await execute() - } - - return { - isFetching, - detailId, - detail: data, - loadDetail, - } -} - -export interface UseDeleteItemConfig extends CommonConfig { - showTip?: MaybeRef -} - -export interface UseDeleteItemReturn { - isFetching: Ref - deleteId: Ref - submitDelete: () => Promise | undefined> -} - -/** - * 封装删除某项 - * @param config.url 请求地址 - * @param config.immediate 是否在修改ID时直接调用删除,默认: `false` - * @param config.showTip 显示提示,默认: `true` - */ -export function useDeleteItem({ - url, - immediate = false, - showTip = true, -}: UseDeleteItemConfig): UseDeleteItemReturn { - const deleteId = ref(undefined) - const _url = computed(() => replaceId(unref(url), deleteId.value)) - const { isFetching, data, execute } = useDelete(_url) - - unref(immediate) && - watch(deleteId, (val) => { - val !== undefined && submitDelete() - }) - - function submitDelete() { - return appConfirm('警告', '您确定要删除该项吗?', { type: 'warning' }) - .then(async () => { - await execute() - - if (data.value) { - deleteId.value = undefined - unref(showTip) && appMessage('success', '删除成功!') - } else { - unref(showTip) && appMessage('warning', '删除失败!') - } - return data - }) - .catch(() => { - deleteId.value = undefined - appMessage('info', '已取消操作') - return undefined - }) - } - - return { - isFetching, - deleteId, - submitDelete, - } -} - -export interface UseListConfig extends CommonConfig { - transform?: (form: T) => T -} - -export interface UseListReturn { - query: Ref - isFetching: Ref - page: Ref - limit: Ref - total: Ref - list: Ref - search: IFormSubmit - loadList: () => Promise -} - -/** - * 封装获取列表 - * @param config.url 请求地址 - * @param config.immediate 是否在初始化时加载,默认: `true` - * @param config.transform 转换搜索 - */ -export function useList({ - url, - transform, - immediate = true, -}: UseListConfig): UseListReturn { - const page = ref(1) - const limit = ref(RequestLimit) - const total = ref(0) - const query = ref({}) as Ref - const payload = ref({}) as Ref - const { isFetching, data, execute } = useGet>(url, payload) - const list = ref([]) as Ref - const search: IFormSubmit = async (done, isValid) => { - if (isValid) { - page.value = 1 - await loadList() - } - done() - } - - unref(immediate) && loadList() - - async function loadList() { - if (isFetching.value) return - setPayload() - await execute() - - if (data.value) { - backtop() - list.value = data.value.list - total.value = data.value.total - } - } - - function setPayload() { - const _query = isFunction(transform) ? transform(unref(query)) : query.value - - payload.value = { - ..._query, - [RequestPageKey]: page.value, - [RequestLimitkey]: limit.value, - } - } - - return { - query, - isFetching, - page, - limit, - total, - list, - search, - loadList, - } -} +import type { UseDetailConfig } from './detail' +import type { UseListReturn } from './list' +import type { UseFormConfig, UseFormReturn } from './form' export interface UseCrudConfig - extends CommonConfig, + extends UseDetailConfig, Pick, 'showTip' | 'transform' | 'afterSubmit'> { syncDetail?: MaybeRef transformQuery?: (form: U) => U @@ -297,7 +40,7 @@ export function useCrud< Item = StringObject, Form = Item, Serach = Item, - Data = unknown + Data = unknown, >({ url, immediate = true, @@ -319,11 +62,12 @@ export function useCrud< transform: transformQuery, }) const { form, submitForm } = useForm({ url, showTip, transform }) - const { deleteId, submitDelete } = useDeleteItem({ url, showTip }) + const { deleteId, submitRemove } = useRemove({ url, showTip }) const { detailId, detail, loadDetail } = useDetail({ url, immediate: false, }) + const beforeOpen: ICrudBeforeOpen = async (done, type, row) => { if (type === 'edit' || type === 'detail') { let value = { ...row } as (Item | Form) & { id?: string | number } @@ -345,6 +89,7 @@ export function useCrud< } done() } + const submit: ICrudSubmit = async (close, done, type, isValid) => { if (isValid) { const res = await submitForm(type === 'add' ? 'post' : 'put') @@ -364,7 +109,7 @@ export function useCrud< async function deleteRow(row: Item) { const _row = row as Item & { id: string | number } deleteId.value = _row.id - const res = await submitDelete() + const res = await submitRemove() res?.value && listCore.loadList() } diff --git a/src/composables/detail.ts b/src/composables/detail.ts new file mode 100644 index 0000000..8d2d763 --- /dev/null +++ b/src/composables/detail.ts @@ -0,0 +1,48 @@ +import type { Ref } from 'vue' +import type { UseFetchReturn } from '@vueuse/core' +import type { UnknownObject, MaybeRef } from 'element-pro-components' + +export interface UseDetailConfig { + url: MaybeRef + immediate?: MaybeRef +} + +export interface UseDetailReturn + extends Omit, 'data' | 'execute'> { + isFetching: Ref + detailId: Ref + detail: Ref + loadDetail: () => Promise +} + +/** + * 封装获取详情 + * @param config.url 请求地址 + * @param config.immediate 是否在修改ID时加载,默认: `true` + */ +export function useDetail({ + url, + immediate = true, +}: UseDetailConfig): UseDetailReturn { + const detailId = ref(undefined) + const _url = computed(() => replaceId(unref(url), detailId.value)) + const { isFetching, data, execute, ...args } = useGet(_url) + + unref(immediate) && + watch(detailId, (val) => { + val !== undefined && loadDetail() + }) + + async function loadDetail() { + if (isFetching.value) return + await execute() + } + + return { + ...args, + isFetching, + detailId, + detail: data, + loadDetail, + } +} diff --git a/src/composables/form.ts b/src/composables/form.ts new file mode 100644 index 0000000..2eff5dc --- /dev/null +++ b/src/composables/form.ts @@ -0,0 +1,92 @@ +import { isFunction } from 'element-pro-components' +import type { Ref } from 'vue' +import type { UseFetchReturn } from '@vueuse/core' +import type { + IFormSubmit, + StringObject, + MaybeRef, +} from 'element-pro-components' + +export type ReqFormType = 'post' | 'put' + +export interface UseFormConfig { + url: MaybeRef + showTip?: MaybeRef + type?: MaybeRef + transform?: (form: T) => T + afterSubmit?: (res: K | null) => void +} + +export interface UseFormReturn extends UseFetchReturn { + form: Ref + submit: IFormSubmit + submitForm: (type?: ReqFormType) => Promise> +} + +/** + * 封装表单提交 + * @param config.url 请求地址 + * @param config.showTip 显示提示,默认: `true` + * @param config.type 提交请求方式 post | put,默认: `post` + * @param config.transform 转换表单 + * @param config.afterSubmit 提交表单后执行的回调函数 + */ +export function useForm({ + url, + transform, + showTip = true, + type = 'post', + afterSubmit, +}: UseFormConfig): UseFormReturn { + const _type = ref(unref(type)) + const form = ref({}) as Ref + const payload = ref({}) as Ref + + const _url = computed(() => { + return replaceId(unref(url), (payload.value as { id?: string }).id) + }) + + const postForm = usePost(url, payload) + const putForm = usePut(_url, payload) + + const submit: IFormSubmit = async (done, isValid) => { + if (isValid) { + const res = await submitForm() + + if (res.value) { + unref(showTip) && appMessage('success', '提交成功!') + } else if (res.value !== null) { + unref(showTip) && appMessage('warning', '提交失败!') + } + isFunction(afterSubmit) && afterSubmit(res.value) + } + done() + } + + async function submitForm(reqType?: ReqFormType) { + if (reqType) { + _type.value = reqType + } + setPayload() + + if (_type.value === 'post') { + await postForm.execute() + return postForm.data + } else { + await putForm.execute() + + return putForm.data + } + } + + function setPayload() { + payload.value = isFunction(transform) ? transform(unref(form)) : form.value + } + + return { + ...(_type.value === 'put' ? putForm : postForm), + form, + submit, + submitForm, + } +} diff --git a/src/composables/list.ts b/src/composables/list.ts new file mode 100644 index 0000000..812c6a3 --- /dev/null +++ b/src/composables/list.ts @@ -0,0 +1,89 @@ +import { isFunction } from 'element-pro-components' +import type { Ref } from 'vue' +import type { UseFetchReturn } from '@vueuse/core' +import type { IFormSubmit, StringObject } from 'element-pro-components' +import type { UseDetailConfig } from './detail' +import type { PagesData } from '../types/index' + +export interface UseListConfig extends UseDetailConfig { + transform?: (form: T) => T +} + +export interface UseListReturn + extends Omit>, 'data' | 'execute'> { + query: Ref + list: Ref + page: Ref + limit: Ref + total: Ref + search: IFormSubmit + loadList: () => Promise +} + +/** + * 封装获取列表 + * @param config.url 请求地址 + * @param config.immediate 是否在初始化时加载,默认: `true` + * @param config.transform 转换搜索 + */ +export function useList({ + url, + transform, + immediate = true, +}: UseListConfig): UseListReturn { + const page = ref(1) + const limit = ref(RequestLimit) + const total = ref(0) + const query = ref({}) as Ref + const payload = ref({}) as Ref + const list = ref([]) as Ref + + const { isFetching, data, execute, ...args } = useGet>( + url, + payload, + ) + + const search: IFormSubmit = async (done, isValid) => { + if (isValid) { + page.value = 1 + await loadList() + } + done() + } + + unref(immediate) && loadList() + + async function loadList() { + if (isFetching.value) return + setPayload() + await execute() + + if (data.value) { + backtop() + list.value = data.value.list + total.value = data.value.total + } + } + + function setPayload() { + const _query = isFunction(transform) ? transform(unref(query)) : query.value + + payload.value = { + ..._query, + [RequestPageKey]: page.value, + [RequestLimitkey]: limit.value, + } + } + + return { + ...args, + query, + isFetching, + page, + limit, + total, + list, + search, + loadList, + } +} diff --git a/src/composables/remove.ts b/src/composables/remove.ts new file mode 100644 index 0000000..0ca195d --- /dev/null +++ b/src/composables/remove.ts @@ -0,0 +1,63 @@ +import type { Ref } from 'vue' +import type { UseFetchReturn } from '@vueuse/core' +import type { MaybeRef } from 'element-pro-components' +import type { UseDetailConfig } from './detail' + +export interface UseRemoveConfig extends UseDetailConfig { + showTip?: MaybeRef +} + +export interface UseRemoveReturn + extends Omit, 'data' | 'execute'> { + deleteId: Ref + submitRemove: () => Promise | undefined> +} + +/** + * 封装删除某项 + * @param config.url 请求地址 + * @param config.immediate 是否在修改ID时直接调用删除,默认: `false` + * @param config.showTip 显示提示,默认: `true` + */ +export function useRemove({ + url, + immediate = false, + showTip = true, +}: UseRemoveConfig): UseRemoveReturn { + const deleteId = ref(undefined) + + const _url = computed(() => replaceId(unref(url), deleteId.value)) + + const { data, execute, ...args } = useDelete(_url) + + unref(immediate) && + watch(deleteId, (val) => { + val !== undefined && submitRemove() + }) + + function submitRemove() { + return appConfirm('警告', '您确定要删除该项吗?', { type: 'warning' }) + .then(async () => { + await execute() + + if (data.value) { + deleteId.value = undefined + unref(showTip) && appMessage('success', '删除成功!') + } else { + unref(showTip) && appMessage('warning', '删除失败!') + } + return data + }) + .catch(() => { + deleteId.value = undefined + appMessage('info', '已取消操作') + return undefined + }) + } + + return { + ...args, + deleteId, + submitRemove, + } +} diff --git a/src/composables/request.ts b/src/composables/request.ts index 399bc6b..2b7c649 100644 --- a/src/composables/request.ts +++ b/src/composables/request.ts @@ -2,13 +2,16 @@ import { stringifyQuery, LocationQueryRaw } from 'vue-router' import { createFetch, isObject, MaybeRef, UseFetchReturn } from '@vueuse/core' import router from '../router/index' +// 登陆是否已经过期 +let isExpiration = false const baseUrl = import.meta.env.VITE_BASE_URL const useRequest = createFetch({ baseUrl, options: { immediate: false, timeout: RequestTimeout, - beforeFetch({ options }) { + beforeFetch({ options, cancel }) { + if (isExpiration) return cancel() const state = useGlobalState() // NOTE: 在线 mock api 使用,你不需要下面这段 @@ -38,9 +41,12 @@ const useRequest = createFetch({ name: '', avatar: '', } - appMessage('warning', '登陆已经过期') + !isExpiration && appMessage('warning', '登录已经过期') + isExpiration = true setTimeout(() => { - router.push('/login') + router + .replace(`/login?redirect=${router.currentRoute.value.path}`) + .then(() => location.reload()) }, 1500) data = null } else if (status === 1000) { @@ -75,7 +81,7 @@ const useRequest = createFetch({ */ export function useGet( url: MaybeRef, - query?: MaybeRef + query?: MaybeRef, ): UseFetchReturn { const _url = computed(() => { const _url = unref(url) @@ -96,7 +102,7 @@ export function useGet( */ export function usePost( url: MaybeRef, - payload?: MaybeRef + payload?: MaybeRef, ): UseFetchReturn { return useRequest(url).post(payload).json() } @@ -108,7 +114,7 @@ export function usePost( */ export function usePut( url: MaybeRef, - payload?: MaybeRef + payload?: MaybeRef, ): UseFetchReturn { return useRequest(url).put(payload).json() } @@ -120,7 +126,7 @@ export function usePut( */ export function useDelete( url: MaybeRef, - payload?: MaybeRef + payload?: MaybeRef, ): UseFetchReturn { return useRequest(url).delete(payload).json() }