diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f93587..dc46205c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,57 @@ # CHANGE LOG +## 4.6.3 + +## Feats + +- `GlobalSearch` 相关 + - 取消递归查找,使用 `getRoutes` 方法替代查找,提高查找速度 + - 现在是用快捷键激活搜索框的时候,如果再次按下快捷键并且搜索框已经是激活状态,则不会重新渲染搜索框 +- 补充 `resolveOption` 方法注释。在使用该方法的时候,需要开发者自己手动补充当前项的 `fullPath` 字段。因为该方法在处理的时候,并不能准确的感知到当前项的 `fullPath` 字段,所以需要手动补充 + +```ts +import { useMenuActions } from '@/store' + +const { resolveOption } = useMenuActions() + +resolveOption({ + // ...your option + fullPath: '/your path', +}) +``` + +- 优化 `GlobalSearch` 组件样式 +- `addStyle` 方法相关 + - 方法重写并且更名为 `setStyle` + - 支持自动补全内核前缀 + - 支持识别样式变量(css var)方式插入 + - 支持字符串数组形式插入样式 +- `queryElements` 方法支持默认值配置项 +- `addClass` 相关 + - 方法更名为 `setClass` + - 支持数组传参 +- `hasClass`, `removeClass` 方法支持数组传参 +- 补充 `pick`, `omit` 方法类型重载 +- `menu store` 相关 + - 新增 `depthSearchAppMenu` 方法,用于深度搜索菜单,并且该方法会缓存上一次的搜索结果 +- 新增 `isAsyncFunction` 方法,用于判断是否为 `async` 函数 + +```ts +import { depthSearchAppMenu } from '@/store' + +const result = depthSearchAppMenu(appMenuOptions, 'target fullPath') +``` + +- `useBadge` 相关 + - 使用 `depthSearchAppMenu` 方法替代原查找方法 + - 将 `equal` 方法提取到 `utils` 包中,并且更名为 `equalRouterPath`,用于判断两个路径是否相等 + +## Fixes + +- 修复 `SettingDrawer` 组件某些开关不能正确同步状态问题 +- 修复 `usePrint` 方法在 `unrefElement` 方法获取失败后不执行的问题。在 `print-js` 逻辑中,如果未获取到 `dom`,会视为其他的打印方式,不符合 `print-js` 的设计 +- `isPromise` 方法修正,现在会正确的识别 `async` 标记d的函数 + ## 4.6.2 ## Feats diff --git a/package.json b/package.json index b9f286d7..7bb35ca2 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ray-template", "private": false, - "version": "4.6.2", + "version": "4.6.3", "type": "module", "engines": { "node": "^18.0.0 || >=20.0.0", diff --git a/src/app-components/provider/AppStyleProvider/index.tsx b/src/app-components/provider/AppStyleProvider/index.tsx index 9c9856ac..cd2c7cff 100644 --- a/src/app-components/provider/AppStyleProvider/index.tsx +++ b/src/app-components/provider/AppStyleProvider/index.tsx @@ -11,9 +11,9 @@ import { get } from 'lodash-es' import { - addClass, + setClass, removeClass, - addStyle, + setStyle, colorToRgba, getStorage, } from '@/utils' @@ -62,7 +62,7 @@ export default defineComponent({ const el = document.getElementById('pre-loading-animation') if (el) { - addStyle(el, { + setStyle(el, { display: 'none', }) } @@ -84,7 +84,7 @@ export default defineComponent({ ? removeClass(body, lightClassName) : removeClass(body, darkClassName) - addClass(body, bool ? darkClassName : lightClassName) + setClass(body, bool ? darkClassName : lightClassName) } syncPrimaryColorToBody() diff --git a/src/app-config/appConfig.ts b/src/app-config/appConfig.ts index 56ba691d..a2c7b8a3 100644 --- a/src/app-config/appConfig.ts +++ b/src/app-config/appConfig.ts @@ -120,6 +120,7 @@ export const APP_CATCH_KEY = { appPiniaSigningStore: 'piniaSigningStore', appVersionProvider: 'appVersionProvider', isAppLockScreen: 'isAppLockScreen', + appGlobalSearchOptions: 'appGlobalSearchOptions', } as const /** diff --git a/src/axios/index.ts b/src/axios/index.ts index 74e17f57..c1741f44 100644 --- a/src/axios/index.ts +++ b/src/axios/index.ts @@ -29,12 +29,14 @@ import type { AppRawRequestConfig } from '@/axios/type' /** * + * @param fetchOption axios 请求配置 + * @param option useRequest 配置项 + * + * @description * 该方法有一定的局限性,仅可在 effect 作用域中使用 * 如果在非 vue effect scope 中使用,会抛出一些警告 * - * 非 vue effect 中使用 * @example - * * // 请求函数 * const getUser = () => request({ url: 'http://localhost:3000/user' }) * diff --git a/src/components/RChart/src/index.tsx b/src/components/RChart/src/index.tsx index 5499c7f0..e8a910bc 100644 --- a/src/components/RChart/src/index.tsx +++ b/src/components/RChart/src/index.tsx @@ -298,7 +298,7 @@ export default defineComponent({ // 避免重复渲染 if (echartInst?.getDom()) { console.warn( - 'RChart mount: There is a chart instance already initialized on the dom. Execution was interrupted.', + '[RChart mount]: There is a chart instance already initialized on the dom. Execution was interrupted.', ) return diff --git a/src/directives/modules/disabled/index.ts b/src/directives/modules/disabled/index.ts index f5e9ba2a..ed29de51 100644 --- a/src/directives/modules/disabled/index.ts +++ b/src/directives/modules/disabled/index.ts @@ -14,7 +14,7 @@ * directive name: disabled */ -import { addClass, removeClass } from '@/utils' +import { setClass, removeClass } from '@/utils' import type { CustomDirectiveFC } from '@/directives/type' @@ -25,7 +25,7 @@ const updateElementDisabledType = (el: HTMLElement, value: boolean) => { if (value) { el.setAttribute('disabled', 'disabled') - addClass(el, classes) + setClass(el, classes) } else { el.removeAttribute('disabled') diff --git a/src/hooks/template/useBadge.ts b/src/hooks/template/useBadge.ts index df4a85f3..a17aa7bf 100644 --- a/src/hooks/template/useBadge.ts +++ b/src/hooks/template/useBadge.ts @@ -1,4 +1,4 @@ -import { useMenuGetters } from '@/store' +import { useMenuGetters, depthSearchAppMenu } from '@/store' import { isValueType } from '@/utils' import { createMenuExtra } from '@/store/modules/menu/helper' @@ -25,64 +25,16 @@ const renderExtra = ( cachePreNormal = option } -/** - * - * @param path1 待比较的路径1 - * @param path2 待比较的路径2 - * - * @description - * 判断两个路径是否相等,忽略最后的斜杠 - * - * @example - * equal('/a/', '/a') // true - * equal('/a', '/a') // true - */ -const equal = (path1: string, path2: string) => { - const path1End = path1.endsWith('/') - const path2End = path2.endsWith('/') - - if (path1End && path2End) { - return path1.slice(0, -1) === path2.slice(0, -1) - } - - if (!path1End && !path2End) { - return path1 === path2 - } - - return ( - path1 === path2 || - path1.slice(0, -1) === path2 || - path1 === path2.slice(0, -1) - ) -} - -// 递归查找匹配的菜单项,缓存上一次的匹配项 -const deep = ( +// 根据匹配的菜单项渲染 extra +const renderExtraWithNormalMenuOption = ( options: AppMenuOption[], target: string, - fn: string, assignExtra: AppMenuExtraOptions, ) => { - if (cachePreNormal && equal(cachePreNormal.fullPath, target)) { - renderExtra(cachePreNormal, assignExtra) + const menuOption = depthSearchAppMenu(options, target) - return cachePreNormal - } - - for (const curr of options) { - if (equal(curr.fullPath, target)) { - renderExtra(curr, assignExtra) - - cachePreNormal = curr - - return curr - } - - if (curr.children?.length) { - deep(curr.children, target, fn, assignExtra) - - continue - } + if (menuOption) { + renderExtra(menuOption, assignExtra) } } @@ -95,11 +47,11 @@ const normalOption = ( const { getMenuOptions } = useMenuGetters() if (typeof target === 'string') { - deep(getMenuOptions.value, target, fn, assignExtra) + renderExtraWithNormalMenuOption(getMenuOptions.value, target, assignExtra) } else if (isValueType<'object'>(target, 'Object')) { const { fullPath } = target - deep(getMenuOptions.value, fullPath, fn, assignExtra) + renderExtraWithNormalMenuOption(getMenuOptions.value, fullPath, assignExtra) } else { console.warn(`[useBadge ${fn}]: target expect string or object.`) } diff --git a/src/hooks/template/useSiderBar.ts b/src/hooks/template/useSiderBar.ts index 7b321367..45313f52 100644 --- a/src/hooks/template/useSiderBar.ts +++ b/src/hooks/template/useSiderBar.ts @@ -212,13 +212,14 @@ export function useSiderBar() { 'path', 'name', 'redirect', - ]) as unknown as AppMenuOption + ]) + const res = resolveOption(pickOption as unknown as AppMenuOption) changeMenuModelValue( - pickOption.path, + res.path, resolveOption({ - ...pickOption, - fullPath: pickOption.path, + ...res, + fullPath: res.path, }), ) } diff --git a/src/hooks/web/useDomToImage.ts b/src/hooks/web/useDomToImage.ts index dcfa7d6e..82585466 100644 --- a/src/hooks/web/useDomToImage.ts +++ b/src/hooks/web/useDomToImage.ts @@ -34,6 +34,8 @@ export interface UseDomToImageOptions extends ReDomToImageOptions { * 在 dom 转换为图片之前执行 * * @param element current dom + * + * @default undefined */ beforeCreate?: ( element: T | null | undefined, @@ -44,6 +46,8 @@ export interface UseDomToImageOptions extends ReDomToImageOptions { * @param result dom to image result * * 在 dom 转换为图片之后执行 + * + * @default undefined */ created?: ( result: DomToImageResult, @@ -54,6 +58,8 @@ export interface UseDomToImageOptions extends ReDomToImageOptions { * @param error dom to image error * * 在 dom 转换为图片失败时执行 + * + * @default undefined */ createdError?: (error?: Error) => void /** @@ -61,6 +67,8 @@ export interface UseDomToImageOptions extends ReDomToImageOptions { * @param element current dom * * 无论 dom 转换为图片成功或失败,都会执行 + * + * @default undefined */ finally?: () => void } @@ -78,12 +86,15 @@ const domToImageMethods = { * @param target ref dom * @param options dom-to-image options * - * 使用 dom-to-image 将 dom 转换为图片,基于 dom-to-image v2.6.0 - * 拓展了 imageType 参数,用于指定图片类型 + * @see https://github.com/tsayen/dom-to-image * - * create 方法支持在执行时传递 imageType 参数,用于指定图片类型。并且优先级大于 options.imageType - * 当然,你也可以不传递 imageType 参数,此时会使用 options.imageType - * 如果都未传递,则默认使用 jpeg + * @description + * 使用 dom-to-image 将 dom 转换为图片,基于 dom-to-image v2.6.0。 + * 拓展了 imageType 参数,用于指定图片类型。 + * + * create 方法支持在执行时传递 imageType 参数,用于指定图片类型。并且优先级大于 options.imageType; + * 当然,你也可以不传递 imageType 参数,此时会使用 options.imageType, + * 如果都未传递,则默认使用 jpeg。 * * @example * const refDom = ref() diff --git a/src/hooks/web/usePrint.ts b/src/hooks/web/usePrint.ts index 342af3be..84619257 100644 --- a/src/hooks/web/usePrint.ts +++ b/src/hooks/web/usePrint.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import print from 'print-js' import { unrefElement } from '@/utils' @@ -7,7 +6,7 @@ import type { BasicTarget } from '@/types' export interface UsePrintOptions extends Omit {} -export type UsePrintTarget = +export type UsePrintTarget = | BasicTarget | string | Blob @@ -19,12 +18,16 @@ export type UsePrintTarget = * @param target ref dom * @param options print-js options * - * 拓展 print-js 的 usePrint 方法,允许 ref Dom 直接调用打印,其余的不变 + * @see https://printjs.crabbly.com/ + * + * @description + * 拓展 print-js 的 usePrint 方法,允许 ref Dom 直接调用打印,其余的不变。 * * @example * const refDom = ref() * * const { print } = usePrint(refDom, {}) + * * @example * const { print } = usePrint('#id', {}) * const { print } = usePrint('base64', {}) // 设置为 base64 时,一定要设置配置项 base64 为 true @@ -33,14 +36,13 @@ export type UsePrintTarget = */ export const usePrint = (target: UsePrintTarget, options?: UsePrintOptions) => { const run = () => { - const element = unrefElement(target as BasicTarget) + // 为了兼容 ref 注册的 dom;如果未获取到 dom,则会视为其他的输出方式,交由 print-js 处理 + const _target = unrefElement(target as BasicTarget) || target - if (element) { - print({ - ...options, - printable: element, - }) - } + print({ + ...options, + printable: _target, + }) } return { diff --git a/src/hooks/web/useVueRouter.ts b/src/hooks/web/useVueRouter.ts index be7b29d1..ff56ab64 100644 --- a/src/hooks/web/useVueRouter.ts +++ b/src/hooks/web/useVueRouter.ts @@ -30,7 +30,9 @@ export const useVueRouter = () => { throw new Error() } } catch (e) { - throw new Error('router is not defined') + throw new Error( + `[useVueRouter]: An error occurred during registration of vue-router. ${e}`, + ) } } diff --git a/src/layout/components/SiderBar/components/GlobalSearch/index.scss b/src/layout/components/SiderBar/components/GlobalSearch/index.scss index 289a292c..ce4b358a 100644 --- a/src/layout/components/SiderBar/components/GlobalSearch/index.scss +++ b/src/layout/components/SiderBar/components/GlobalSearch/index.scss @@ -12,7 +12,7 @@ $globalSearchWidth: 650px; & .global-search__card { border-radius: 6px; - min-width: 800px; + min-width: 560px; & .ray-icon { color: var(--ray-theme-primary-color); @@ -22,8 +22,9 @@ $globalSearchWidth: 650px; padding: 16px 12px 12px 12px; } - & .n-card__content { - min-height: 115px; + & .n-card__content, + & .n-spin-content { + min-height: 90px; } & .content-item { diff --git a/src/layout/components/SiderBar/components/GlobalSearch/index.tsx b/src/layout/components/SiderBar/components/GlobalSearch/index.tsx index 80e8061a..b9dacd91 100644 --- a/src/layout/components/SiderBar/components/GlobalSearch/index.tsx +++ b/src/layout/components/SiderBar/components/GlobalSearch/index.tsx @@ -31,9 +31,9 @@ import { } from 'naive-ui' import { RIcon } from '@/components' -import { queryElements, addClass, removeClass } from '@/utils' +import { queryElements, setClass, removeClass, pick } from '@/utils' import { debounce } from 'lodash-es' -import { useMenuGetters, useMenuActions } from '@/store' +import { useMenuActions } from '@/store' import { validMenuItemShow } from '@/router/helper/routerCopilot' import { useDevice } from '@/hooks' import { useEventListener } from '@vueuse/core' @@ -51,7 +51,9 @@ export default defineComponent({ }, emits: ['update:show'], setup(props, { emit }) { - const { changeMenuModelValue } = useMenuActions() + const { changeMenuModelValue, resolveOption } = useMenuActions() + const { getRoutes } = useRouter() + const modelShow = computed({ get: () => props.show, set: (val) => { @@ -62,8 +64,6 @@ export default defineComponent({ } }, }) - const { getMenuOptions } = useMenuGetters() - const state = reactive({ searchValue: null, searchOptions: [] as AppMenuOption[], @@ -102,6 +102,10 @@ export default defineComponent({ /** 按下 ctrl + k 或者 command + k 激活搜索栏 */ const registerArouseKeyboard = (e: KeyboardEvent) => { + if (modelShow.value) { + return + } + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault() e.stopPropagation() @@ -113,8 +117,6 @@ export default defineComponent({ /** 根据输入值模糊检索菜单 */ const fuzzySearchMenuOptions = (value: string) => { - const arr: AppMenuOption[] = [] - if (value) { loading.value = true } else { @@ -124,37 +126,35 @@ export default defineComponent({ return } - const filterArr = (options: AppMenuOption[]) => { - for (const curr of options) { - if (curr.children?.length && validMenuItemShow(curr)) { - filterArr(curr.children) + const arr = getRoutes().reduce((pre, curr) => { + const pickOption = pick(curr, [ + 'children', + 'meta', + 'path', + 'name', + ]) as unknown as AppMenuOption - continue - } + const res = resolveOption({ + ...pickOption, + fullPath: curr.path, + }) + const { breadcrumbLabel } = res - /** 处理菜单名与输入值, 不区分大小写 */ - const $breadcrumbLabel = curr.breadcrumbLabel?.toLocaleLowerCase() - const $value = String(value).toLocaleLowerCase() - - // 是否模糊匹配字符、满足展示条件 - if ( - $breadcrumbLabel?.includes($value) && - validMenuItemShow(curr) && - !curr.children?.length - ) { - arr.push(curr) - } + // 是否模糊匹配字符、满足展示条件 + if ( + breadcrumbLabel + ?.toLocaleLowerCase() + ?.includes(value.toLocaleLowerCase()) && + validMenuItemShow(res) + ) { + pre.push(res) } - } + + return pre + }, [] as AppMenuOption[]) setTimeout(() => { - if (value) { - filterArr(getMenuOptions.value) - - state.searchOptions = arr - } else { - state.searchOptions = [] - } + state.searchOptions = arr nextTick().then(() => { autoFocusingSearchItem() @@ -203,7 +203,7 @@ export default defineComponent({ if (searchElementOptions?.length) { const [el] = searchElementOptions - addClass(el, activeClass) + setClass(el, activeClass) } }) } @@ -218,7 +218,7 @@ export default defineComponent({ } else if (typeof icon === 'function') { return () => icon } else { - return + return } } @@ -381,8 +381,7 @@ export default defineComponent({ justify="center" class="global-search__empty-content" > - - 暂无搜索结果 + 没有搜索结果 ), }} diff --git a/src/layout/components/SiderBar/components/GlobalSearchButton/index.tsx b/src/layout/components/SiderBar/components/GlobalSearchButton/index.tsx index 14be4599..4ecdaa05 100644 --- a/src/layout/components/SiderBar/components/GlobalSearchButton/index.tsx +++ b/src/layout/components/SiderBar/components/GlobalSearchButton/index.tsx @@ -30,7 +30,7 @@ export default defineComponent({ const operatingSystem = detectOperatingSystem() if (operatingSystem === 'MacOS') { - return '⌘ K' + return '⌘ + K' } if (operatingSystem === 'Windows') { diff --git a/src/layout/components/SiderBar/components/SettingDrawer/index.tsx b/src/layout/components/SiderBar/components/SettingDrawer/index.tsx index 5fd82d48..8a2a8f2f 100644 --- a/src/layout/components/SiderBar/components/SettingDrawer/index.tsx +++ b/src/layout/components/SiderBar/components/SettingDrawer/index.tsx @@ -64,12 +64,18 @@ export default defineComponent({ emit('update:show', bool) }, }) - const modelSwitchReactive = reactive({ - getMenuTagSwitch: getMenuTagSwitch.value, - getBreadcrumbSwitch: getBreadcrumbSwitch.value, - getCopyrightSwitch: getCopyrightSwitch.value, - getContentTransition: getContentTransition.value, - getWatermarkSwitch: getWatermarkSwitch.value, + // 为了方便管理多个 computed,因为 computed 不能被逆向修改 + const modelSwitchReactive = computed({ + get: () => { + return { + getMenuTagSwitch: getMenuTagSwitch.value, + getBreadcrumbSwitch: getBreadcrumbSwitch.value, + getCopyrightSwitch: getCopyrightSwitch.value, + getContentTransition: getContentTransition.value, + getWatermarkSwitch: getWatermarkSwitch.value, + } + }, + set: (value) => {}, }) return { diff --git a/src/router/modules/demo/multi-menu.ts b/src/router/modules/demo/multi-menu.ts index d81b6897..90a39cab 100644 --- a/src/router/modules/demo/multi-menu.ts +++ b/src/router/modules/demo/multi-menu.ts @@ -11,6 +11,9 @@ const multiMenu: AppRouteRecordRaw = { i18nKey: t('menu.MultiMenu'), icon: 'other', order: 4, + extra: { + label: 'cache', + }, }, children: [ { diff --git a/src/router/routes.ts b/src/router/routes.ts index e05cf775..8c8fd4fd 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -13,8 +13,8 @@ export default async () => { */ { path: '/', - name: 'login', - component: () => import('@/views/login/index'), + name: 'RLogin', + component: () => import('@/views/login'), }, /** * @@ -22,7 +22,7 @@ export default async () => { */ { path: '/', - name: 'layout', + name: 'RLayout', redirect: getRootPath.value, component: Layout, children: appExpandRoutes(), diff --git a/src/store/modules/menu/index.ts b/src/store/modules/menu/index.ts index ade801ce..2a687cf3 100644 --- a/src/store/modules/menu/index.ts +++ b/src/store/modules/menu/index.ts @@ -25,7 +25,7 @@ import { NEllipsis } from 'naive-ui' -import { setStorage, pick } from '@/utils' +import { setStorage, pick, equalRouterPath } from '@/utils' import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot' import { parseAndFindMatchingNodes, @@ -43,6 +43,47 @@ import type { AppMenuOption, MenuTagOptions } from '@/types' import type { MenuState } from '@/store/modules/menu/type' import type { LocationQuery } from 'vue-router' +let cachePreNormal: AppMenuOption | undefined = void 0 + +/** + * + * @param options 菜单列表或者类似菜单列表的数据结构 + * @param target 目标路径 + * + * @returns 匹配的菜单项 + * + * @description + * 递归查找匹配的菜单项,缓存上一次的匹配项。 + * 并且该方法一旦匹配成功就会立即返回。 + * + * 通过 fullPath 进行匹配。 + * + * @example + * depthSearchAppMenu([{ path: '/dashboard', name: 'Dashboard', meta: { i18nKey: 'menu.Dashboard' } }], '/dashboard') + */ +export const depthSearchAppMenu = ( + options: AppMenuOption[], + target: string, +) => { + if (cachePreNormal && equalRouterPath(cachePreNormal.fullPath, target)) { + return cachePreNormal + } + + for (const curr of options) { + if (equalRouterPath(curr.fullPath, target)) { + cachePreNormal = curr + + return curr + } + + if (curr.children?.length) { + depthSearchAppMenu(curr.children, target) + + continue + } + } +} + export const piniaMenuStore = defineStore( 'menu', () => { @@ -64,9 +105,13 @@ export const piniaMenuStore = defineStore( /** * * @param option 菜单项(类似于菜单项的数据结构也可以) + * * @returns 转换后的菜单项 * - * 将路由项或者类似于菜单项的数据结构转换为菜单项(AppMenu) + * @description + * 将路由项或者类似于菜单项的数据结构转换为菜单项(AppMenu)。 + * 但是,该方法有一个地方需要注意,那就是需要手动设置一下准确的 fullPath, + * 其实这是一个设计的失误,因为该方法不能准确的感知到 fullPath 应该是什么。 * * @example * resolveOption({ path: '/dashboard', name: 'Dashboard', meta: { i18nKey: 'menu.Dashboard' } }) diff --git a/src/types/modules/utils.ts b/src/types/modules/utils.ts index 4566c72d..3530b258 100644 --- a/src/types/modules/utils.ts +++ b/src/types/modules/utils.ts @@ -115,3 +115,11 @@ export interface StorageOptions { prefixKey?: string defaultValue?: T } + +export interface QueryElementsOptions { + defaultElement?: T +} + +export type PropertyName = string | number | symbol + +export type Many = T | ReadonlyArray diff --git a/src/utils/basic.ts b/src/utils/basic.ts index f6f86235..500a0df7 100644 --- a/src/utils/basic.ts +++ b/src/utils/basic.ts @@ -5,23 +5,30 @@ import type { DownloadAnyFileDataType, BasicTypes, AnyFC, + PropertyName, + Recordable, + Many, } from '@/types' -import type { Recordable } from '@/types' /** * - * 获取当前项目环境 + * @description + * 获取当前项目环境。 * - * 如果你只是想单纯的判断是否为开发环境,可以直接使用: __DEV__ + * 如果你只是想单纯的判断是否为开发环境,可以直接使用: __DEV__。 * * @example * 是否为开发环境: __DEV__ * * @example - * const { BASE_URL } = getAppEnvironment() 获取 BASE_URL - * const { MODE } = getAppEnvironment() 获取 MODE,当前环境 - * const { SSR } = getAppEnvironment() 是否启用 SSR - * const { your config } = getAppEnvironment() 获取你自定义的配置项 + * // 获取 BASE_URL + * const { BASE_URL } = getAppEnvironment() + * // 获取 MODE,当前环境 + * const { MODE } = getAppEnvironment() + * // 是否启用 SSR + * const { SSR } = getAppEnvironment() + * // 获取你自定义的配置项 + * const { your config } = getAppEnvironment() */ export const getAppEnvironment = () => { const env = import.meta.env @@ -33,10 +40,11 @@ export const getAppEnvironment = () => { * * @param data 二进制流数据 * - * 将 base64 格式文件转换为图片 + * @description + * 将二进制流数据转换为 base64 图片。 * * @example - * arrayBufferToBase64Image('base64') => Image + * const Image = arrayBufferToBase64Image('base64') */ export const arrayBufferToBase64Image = (data: ArrayBuffer): string | null => { if (!data || data.byteLength) { @@ -60,7 +68,8 @@ export const arrayBufferToBase64Image = (data: ArrayBuffer): string | null => { * @param base64 base64 * @param fileName file name * - * 该方法仅能下载 base64 文件,如果有其他的文件类型需要下载,请看 downloadAnyFile 方法 + * @description + * 该方法仅能下载 base64 文件,如果有其他的文件类型需要下载,请看 downloadAnyFile 方法。 * * @example * downloadBase64File('base64', 'file name') @@ -84,8 +93,10 @@ export const downloadBase64File = (base64: string, fileName: string) => { * @param type 类型 * * @example - * isValueType('123', 'String') => true - * isValueType({}, 'Object') => true + * isValueType('123', 'String') // true + * isValueType({}, 'Object') // true + * isValueType([], 'Array') // true + * isValueType([], 'Object') // false */ export const isValueType = ( value: unknown, @@ -101,6 +112,9 @@ export const isValueType = ( * @param length uuid 长度 * @param radix uuid 基数 * + * @description + * 生成指定长度的 uuid。 + * * @example * uuid(8) => 'B8tGcl0FCKJkpO0V' */ @@ -136,7 +150,8 @@ export const uuid = (length = 16, radix = 62) => { * @param data base64, Blob, ArrayBuffer type * @param fileName file name * - * 支持下载任意类型的文件,包括 base64, Blob, ArrayBuffer + * @description + * 支持下载任意类型的文件,包括 base64, Blob, ArrayBuffer。 * * @example * downloadAnyFile('base64', 'file name') @@ -195,24 +210,40 @@ export const downloadAnyFile = ( }) } +export function omit( + targetObject: T, + ...paths: K +): Pick> + +export function omit( + object: T | null | undefined, + ...paths: Array> +): Partial + /** * * @param targetObject 对象 * @param targetKeys 待删除的 key * - * 删除对象中的指定 key - * 如果传递的 targetObject 为 null 或者 undefined,则返回空对象 + * @description + * 删除对象中的指定 key, + * 如果传递的 targetObject 为 null 或者 undefined,则返回空对象。 * * @example - * omit({ a: 1, b: 2, c: 3 }, 'a') => { b: 2, c: 3 } - * omit({ a: 1, b: 2, c: 3 }, ['a', 'b']) => { c: 3 } + * omit({ a: 1, b: 2, c: 3 }, 'a') // { b: 2, c: 3 } + * omit({ a: 1, b: 2, c: 3 }, ['a', 'b']) // { c: 3 } + * omit(null) // {} */ -export const omit = ( +export function omit( targetObject: T, targetKeys: K | K[], -): Omit => { +) { if (!targetObject) { - return {} as Omit + console.warn( + `[omit]: The targetObject is expected to be an object, but got ${targetObject}.`, + ) + + return {} } const keys = Array.isArray(targetKeys) ? targetKeys : [targetKeys] @@ -228,53 +259,94 @@ export const omit = ( return targetObject } +export function pick( + object: T | null | undefined, + ...paths: Array> +): Partial + /** * * @param targetObject target object * @param targetKeys target keys * - * 从对象中提取指定的 key - * 如果传递的 targetObject 为 null 或者 undefined,则返回空对象 + * @description + * 从对象中提取指定的 key, + * 如果传递的 targetObject 为 null 或者 undefined,则返回空对象。 * * @example - * pick({ a: 1, b: 2, c: 3 }, 'a') => { a: 1 } - * pick({ a: 1, b: 2, c: 3 }, ['a', 'b']) => { a: 1, b: 2 } - * pick({ a: 1, b: 2, c: 3 }, []) => {} + * pick({ a: 1, b: 2, c: 3 }, 'a') // { a: 1 } + * pick({ a: 1, b: 2, c: 3 }, ['a', 'b']) // { a: 1, b: 2 } + * pick({ a: 1, b: 2, c: 3 }, []) // {} + * pick(null) // {} */ -export const pick = ( +export function pick( targetObject: T, targetKeys: K | K[], -): Pick => { +) { if (!targetObject) { - return {} as Pick + console.warn( + `[pick]: The targetObject is expected to be an object, but got ${targetObject}.`, + ) + + return {} } const keys = Array.isArray(targetKeys) ? targetKeys : [targetKeys] - const result = {} as Pick if (!keys.length) { - return result + return targetObject } - keys.forEach((key) => { - result[key] = targetObject[key] - }) + const result = keys.reduce( + (pre, curr) => { + if (Reflect.has(targetObject, curr)) { + pre[curr] = targetObject[curr] + } + + return pre + }, + {} as Pick, + ) return result } /** * - * @param value 待判断的值 + * @param func 待判断的函数 + * @returns 是否为 async 函数 * - * 判断是否为 Promise 函数 + * @description + * 判断是否为 async 函数。 * * @example - * isPromise(Promise.resolve(123)) => true - * isPromise(() => {}) => false - * isPromise(123) => false + * isAsyncFunction(() => {}) // false + * isAsyncFunction(async () => {}) // true + * isAsyncFunction(function() {}) // false + * isAsyncFunction(async function() {}) // true + */ +export const isAsyncFunction = (func: T) => { + return func instanceof Function && func.constructor.name === 'AsyncFunction' +} + +/** + * + * @param value 待判断的值 + * + * @description + * 判断是否为 Promise 函数。 + * + * @example + * isPromise(Promise.resolve(123)) // true + * isPromise(() => {}) // false + * isPromise(123) // false + * isPromise(async () => {}) // true */ export const isPromise = (value: unknown): value is Promise => { + if (isAsyncFunction(value)) { + return true + } + return ( !!value && (typeof value === 'object' || typeof value === 'function') && @@ -288,7 +360,8 @@ export const isPromise = (value: unknown): value is Promise => { * @param errorCallback 错误回调 * @param args 当前传递函数参数 * - * 用于捕获函数执行时的错误,如果有错误,则执行错误回调 + * @description + * 用于捕获函数执行时的错误,如果有错误,则执行错误回调。 * * @example * callWithErrorHandling((x: number) => { return x }, () => {}, [123]) => 123 @@ -316,7 +389,8 @@ export const callWithErrorHandling = ( * @param errorCallback 错误回调 * @param args 当前传递函数参数 * - * 用于捕获异步函数执行时的错误,如果有错误,则执行错误回调 + * @description + * 用于捕获异步函数执行时的错误,如果有错误,则执行错误回调。 * * @example * callWithAsyncErrorHandling(async () => { console.log('A') }, () => {}, []) => Promise { undefined } @@ -346,8 +420,10 @@ export const callWithAsyncErrorHandling = async < /** * - * 获取当前操作系统 - * 如果无法识别,则返回 Unknown + * @description + * 获取当前操作系统。 + * + * 如果无法识别,则返回 Unknown。 * * @example * detectOperatingSystem() => 'Windows' | 'MacOS' | 'Linux' | 'Android' | 'IOS' | 'Unknown' @@ -377,3 +453,36 @@ export const detectOperatingSystem = () => { return OperatingSystem.Unknown } + +/** + * + * @param path1 待比较的路径1 + * @param path2 待比较的路径2 + * + * @returns 比较两个路径是否相等 + * + * @description + * 判断两个路径是否相等,忽略最后的斜杠。 + * + * @example + * equal('/a/', '/a') // true + * equal('/a', '/a') // true + */ +export const equalRouterPath = (path1: string, path2: string) => { + const path1End = path1.endsWith('/') + const path2End = path2.endsWith('/') + + if (path1End && path2End) { + return path1.slice(0, -1) === path2.slice(0, -1) + } + + if (!path1End && !path2End) { + return path1 === path2 + } + + return ( + path1 === path2 || + path1.slice(0, -1) === path2 || + path1 === path2.slice(0, -1) + ) +} diff --git a/src/utils/dom.ts b/src/utils/dom.ts index fe134df3..9bf3a763 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -25,14 +25,15 @@ export interface PrintDomOptions { * @param target ref dom * @param options print-dom options, dom-to-image options * - * 基于 useDomToImage 和 print-js 封装,允许 Ref 注册 Dom 直接调用打印 - * 避免直接打印 dom 时出现一些诡异的问题 + * @description + * 基于 useDomToImage 和 print-js 封装,允许 Ref 注册 Dom 直接调用打印。 + * 避免直接打印 dom 时出现一些诡异的问题。 * - * 该方法会强制剔除 printOptions 中的 printable, type, base64 属性,即使忽略了 ts 的类型检查 - * 并且在绘制图片的时候,强制使用 jpeg 格式,即使是指定了其他格式 + * 该方法会强制剔除 printOptions 中的 printable, type, base64 属性,即使忽略了 ts 的类型检查。 + * 并且在绘制图片的时候,强制使用 jpeg 格式,即使是指定了其他格式。 * - * 支持 useDomToImage 方法的所有配置项,除了 imageType - * 支持 print-js 的所有配置项,除了 printable 和 type + * 支持 useDomToImage 方法的所有配置项,除了 imageType。 + * 支持 print-js 的所有配置项,除了 printable 和 type。 * * @example * const refDom = ref() diff --git a/src/utils/element.ts b/src/utils/element.ts index 413f66e2..8a09e584 100644 --- a/src/utils/element.ts +++ b/src/utils/element.ts @@ -1,29 +1,37 @@ import { APP_REGEX } from '@/app-config' import { effectDispose, unrefElement, isValueType } from '@/utils' -import type { PartialCSSStyleDeclaration, ElementSelector } from '@/types' -import type { BasicTarget } from '@/types' +import type { + BasicTarget, + QueryElementsOptions, + ElementSelector, +} from '@/types' /** * - * @param target Target element dom - * @param className 所需添加className,可: 'xxx xxx' | 'xxx' 格式添加(参考向元素绑定 css 语法) + * @param target 目标元素或 ref 注册元素 + * @param classNames 所需添加类名 * - * 添加元素className(可: 'xxx xxx' | 'xxx'格式添加) + * @description + * 为目标元素添加类名 * * @example - * targetDom 当前 class: a-class b-class - * addClass(targetDom, 'c-class') => a-class b-class c-class + * // targetDom 当前 class: a-class b-class + * setClass(targetDom, 'c-class') // a-class b-class c-class + * setClass(targetDom, ['c-class', 'c-class']) // a-class b-class c-class */ -export const addClass = ( +export const setClass = ( target: BasicTarget, - className: string, + classNames: string | string[], ) => { const update = () => { const element = unrefElement(target) if (element) { - const classes = className.trim().split(' ') + const classes = + typeof classNames === 'string' + ? classNames.trim().split(' ') + : classNames classes.forEach((item) => { if (item) { @@ -42,30 +50,35 @@ export const addClass = ( /** * - * @param target Target element dom - * @param className 所需删除className,可: 'xxx xxx' | 'xxx' 格式删除(参考向元素绑定 css 语法) + * @param target 目标元素或 ref 注册元素 + * @param className 所需删除类名 * - * 删除元素className(可: 'xxx xxx' | 'xxx'格式删除) - * 如果输入值为 removeAllClass 则会删除该元素所有 class name + * @description + * 为目标元素删除类名 * * @example - * targetDom 当前 class: a-class b-class - * removeClass(targetDom, 'a-class') => b-class + * // targetDom 当前 class: a-class b-class + * removeClass(targetDom, 'a-class') // b-class + * removeClass(targetDom, ['a-class', 'b-class']) // null + * removeClass(targetDom, 'removeAllClass') // null */ export const removeClass = ( target: BasicTarget, - className: string | 'removeAllClass', + classNames: string | 'removeAllClass' | string[], ) => { const update = () => { const element = unrefElement(target) if (element) { - if (className === 'removeAllClass') { + if (classNames === 'removeAllClass') { const classList = element.classList classList.forEach((curr) => classList.remove(curr)) } else { - const classes = className.trim().split(' ') + const classes = + typeof classNames === 'string' + ? classNames.trim().split(' ') + : classNames classes.forEach((item) => { if (item) { @@ -85,15 +98,20 @@ export const removeClass = ( /** * - * @param target Target element dom - * @param className 查询元素是否含有此className,可: 'xxx xxx' | 'xxx' 格式查询(参考向元素绑定 css 语法) + * @param target 目标元素或 ref 注册元素 + * @param className 查询元素是否含有此类名 * - * 元素是否含有某个className(可: 'xxx xxx' | 'xxx' 格式查询) + * @description + * 查询元素是否含有此类名 * * @example - * hasClass(targetDom, 'matchClassName') => Ref | Ref + * hasClass(targetDom, 'matchClassName') // Ref | Ref + * hasClass(targetDom, ['matchClassName', 'matchClassName']) // Ref | Ref */ -export const hasClass = (target: BasicTarget, className: string) => { +export const hasClass = ( + target: BasicTarget, + classNames: string | string[], +) => { const hasClassRef = ref(false) const update = () => { @@ -104,12 +122,17 @@ export const hasClass = (target: BasicTarget, className: string) => { } else { const elementClassName = element.className - const classes = className - .trim() - .split(' ') - .filter((item: string) => item !== '') + const classes = + typeof classNames === 'string' + ? classNames + .trim() + .split(' ') + .filter((curr: string) => curr !== '') + : classNames - hasClassRef.value = elementClassName.includes(classes.join(' ')) + hasClassRef.value = classes.some((curr) => + elementClassName.includes(curr), + ) } } @@ -122,17 +145,45 @@ export const hasClass = (target: BasicTarget, className: string) => { return hasClassRef } +/** + * + * @param style 所需添加样式 + * + * @returns 添加前缀后的样式 + * + * @description + * 为样式添加浏览器前缀,返回一个对象 + * + * @example + * autoPrefixStyle('transform') => {webkitTransform: 'transform', mozTransform: 'transform', msTransform: 'transform', oTransform: 'transform'} + */ +export const autoPrefixStyle = (style: string) => { + const prefixes = ['webkit', 'moz', 'ms', 'o'] + const styleWithPrefixes = {} + + prefixes.forEach((prefix) => { + styleWithPrefixes[ + `${prefix}${style.charAt(0).toUpperCase()}${style.slice(1)}` + ] = style + }) + + return styleWithPrefixes +} + /** * * @param target Target element dom * @param styles 所需绑定样式(如果为字符串, 则必须以分号结尾每个行内样式描述) * + * @description + * 为目标元素添加样式 + * * @example * style of string * ``` * const styles = 'width: 100px; height: 100px; background: red;' * - * addStyle(styles) + * setStyle(styles) * ``` * style of object * ``` @@ -141,14 +192,37 @@ export const hasClass = (target: BasicTarget, className: string) => { * height: '100px', * } * - * addStyle(styles) + * setStyle(styles) * ``` */ -export const addStyle = ( +export const setStyle = ( target: BasicTarget, - styles: PartialCSSStyleDeclaration | string, + styles: Partial | string | string[], ) => { - let styleObj: PartialCSSStyleDeclaration + const set = (styleStr: string, element: HTMLElement | SVGAElement) => { + styleStr.split(';').forEach((curr) => { + const [key, value] = curr.split(':') + + if (key && value) { + const trimKey = key.trim() + const trimValue = value.trim() + + // 是否为 css variable + if (key.startsWith('--')) { + element.style.setProperty(trimKey, trimValue) + } else { + // 兼容浏览器前缀 + const kitFix = autoPrefixStyle(trimKey) + + Object.keys(kitFix).forEach((key) => { + element.style[key] = kitFix[key] + }) + // 设置默认需要添加样式 + element.style[trimKey] = trimValue + } + } + }) + } const update = () => { const element = unrefElement(target) @@ -158,26 +232,18 @@ export const addStyle = ( } if (isValueType(styles, 'String')) { - styleObj = styles.split(';').reduce((pre, curr) => { - const [key, value] = curr.split(':').map((s) => s.trim()) - - if (key && value) { - pre[key] = value - } - - return pre - }, {} as PartialCSSStyleDeclaration) + set(styles, element) + } else if (isValueType(styles, 'Array')) { + styles.forEach((curr) => { + set(curr, element) + }) } else { - styleObj = styles + const keys = Object.keys(styles) + + keys.forEach((curr) => { + set(`${curr}: ${styles[curr]}`, element) + }) } - - Object.keys(styleObj).forEach((key) => { - const value = styleObj[key] - - if (key in element!.style) { - element!.style[key] = value - } - }) } const watcher = watch(() => unrefElement(target), update, { @@ -192,6 +258,9 @@ export const addStyle = ( * @param el Target element dom * @param styles 所需卸载样式 * + * @description + * 为目标元素卸载样式 + * * 当你发现不能正常的移除某些样式的时候,应该考虑是否是样式表兼容问题 * * @example @@ -225,14 +294,15 @@ export const removeStyle = ( * @param color 颜色格式 * @param alpha 透明度 * + * @description * 将任意颜色值转为 rgba,如果本身为 rgba, rgb 或者其它非法颜色值则直接返回 * * @example - * colorToRgba('#123632', 0.8) => rgba(18, 54, 50, 0.8) - * colorToRgba('rgb(18, 54, 50)', 0.8) => rgb(18, 54, 50) - * colorToRgba('#ee4f12', 0.3) => rgba(238, 79, 18, 0.3) - * colorToRgba('rgba(238, 79, 18, 0.3)', 0.3) => rgba(238, 79, 18, 0.3) - * colorToRgba('not a color', 0.3) => not a color + * colorToRgba('#123632', 0.8) // rgba(18, 54, 50, 0.8) + * colorToRgba('rgb(18, 54, 50)', 0.8) // rgb(18, 54, 50) + * colorToRgba('#ee4f12', 0.3) // rgba(238, 79, 18, 0.3) + * colorToRgba('rgba(238, 79, 18, 0.3)', 0.3) // rgba(238, 79, 18, 0.3) + * colorToRgba('not a color', 0.3) // not a color */ export const colorToRgba = (color: string, alpha = 1) => { const hexPattern = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i @@ -271,24 +341,31 @@ export const colorToRgba = (color: string, alpha = 1) => { * @param element 需要匹配元素参数名称 * @returns 匹配元素列表 * - * @remark 使用 querySelectorAll 作为检索方法 - * @remark 如果希望按照 attribute 匹配, 仅需要 'attr:xxx'传递参数即可 + * @description + * 使用 querySelectorAll 作为检索方法。 + * + * 如果希望按照 attribute 匹配, 仅需要 'attr:xxx'传递参数即可 * * @example - * class: + * // class: * const el = queryElements('.demo') - * id: + * // id: * const el = queryElements('#demo') - * attribute: + * // attribute: * const el = queryElements('attr:type=button') - * 或者可以这样写 + * // 或者可以这样写 * const el = queryElements('attr:type') + * // 默认元素 + * const el = queryElements('.demo', { defaultElement: document.body }) */ export const queryElements = ( selector: ElementSelector, + options?: QueryElementsOptions, ) => { + const { defaultElement } = options || {} + if (!selector) { - return null + return defaultElement ? [defaultElement] : null } const queryParam = selector.startsWith('attr:') @@ -300,9 +377,12 @@ export const queryElements = ( return elements } catch (error) { - console.error(`Failed to get elements for selector '${selector}'`, error) + console.error( + `[queryElements]: Failed to get elements for selector '${selector}'`, + error, + ) - return null + return defaultElement ? [defaultElement] : null } } @@ -311,7 +391,8 @@ export const queryElements = ( * @param size css size * @param unit 自动填充 css 尺寸单位 * - * @remark 自动补全尺寸 + * @description + * 自动补全尺寸 */ export const completeSize = (size: number | string, unit = 'px') => { if (typeof size === 'number') { diff --git a/src/utils/precision.ts b/src/utils/precision.ts index b540af98..4491ac19 100644 --- a/src/utils/precision.ts +++ b/src/utils/precision.ts @@ -67,7 +67,8 @@ const currencyPrototypeKeys = [ * @param dividend 初始值 * @param cb 回调方法 * - * @remark 计算基础方法, 仅限于该处使用 + * @description + * 计算基础方法, 仅限于该处使用。 */ const basic = ( valueOptions: CurrencyArguments[], @@ -99,10 +100,10 @@ const basic = ( * 当该对象含有 s, intValue, p, value... 属性时, 则认为该对象为 currency.js 的对象 * * @example - * isCurrency(1.23) => false - * isCurrency('1.23') => false - * isCurrency({ s: 1, intValue: 1, p: 1, value: 1 }) => false - * isCurrency(currency(1)) => true + * isCurrency(1.23) // false + * isCurrency('1.23') // false + * isCurrency({ s: 1, intValue: 1, p: 1, value: 1 }) // false + * isCurrency(currency(1)) // true */ export const isCurrency = (value: unknown) => { if (typeof value === 'string' || typeof value === 'number') { @@ -118,10 +119,16 @@ export const isCurrency = (value: unknown) => { /** * - * 格式化一个数据值, 并且返回其原始值 - * 默认以 number 格式返回 + * @description + * 格式化一个数据值, 并且返回其原始值。 * - * 如果需要格式化为其他格式(如: 货币单位、分组、分隔符等), 请使用 currency format 方法格式 + * 默认以 number 格式返回。 + * + * 如果需要格式化为其他格式(如: 货币单位、分组、分隔符等), 请使用 currency format 方法格式。 + * + * @example + * format(0.1) // 0.1 + * format(0.1, { symbol: '¥' }) // ¥0.1 */ export const format = ( value: CurrencyArguments, @@ -136,11 +143,12 @@ export const format = ( /** * - * 加法 + * @description + * 加法。 * * @example - * format(add(0.1, 0.2)) => 0.3 - * format(add(0.2, 0.33)) => 0.53 + * format(add(0.1, 0.2)) // 0.3 + * format(add(0.2, 0.33)) // 0.53 */ export const add = (...args: CurrencyArguments[]) => { if (args.length === 1) { @@ -154,11 +162,12 @@ export const add = (...args: CurrencyArguments[]) => { /** * - * 减法 + * @description + * 减法。 * * @example - * format(subtract(0.1, 0.12312)) => -0.02 - * format(subtract(0.2, 0.33)) => -0.13 + * format(subtract(0.1, 0.12312)) // -0.02 + * format(subtract(0.2, 0.33)) // -0.13 */ export const subtract = (...args: CurrencyArguments[]) => { if (args.length === 1) { @@ -185,11 +194,12 @@ export const subtract = (...args: CurrencyArguments[]) => { /** * - * 乘法 + * @description + * 乘法。 * * @example - * format(multiply(1, 0.2)) => 0.2 - * format(multiply(0.2, 0.33)) => 0.07 + * format(multiply(1, 0.2)) // 0.2 + * format(multiply(0.2, 0.33)) // 0.07 */ export const multiply = (...args: CurrencyArguments[]) => { if (args.length === 1) { @@ -203,11 +213,12 @@ export const multiply = (...args: CurrencyArguments[]) => { /** * - * 除法 + * @description + * 除法。 * * @example - * format(divide(1, 0.2)) => 5 - * format(divide(0.2, 0.33)) => 0.61 + * format(divide(1, 0.2)) // 5 + * format(divide(0.2, 0.33)) // 0.61 */ export const divide = (...args: CurrencyArguments[]) => { if (args.length === 1) { @@ -230,12 +241,13 @@ export const divide = (...args: CurrencyArguments[]) => { /** * - * 平分(将一个数值平均分配到一个数组中) + * @description + * 平分(将一个数值平均分配到一个数组中), * 如果值为 undefined null 会自动转换为 0 * * @example - * distribute(0, 1) => [0] - * distribute(0, 3) => [0, 0, 0] + * distribute(0, 1) // [0] + * distribute(0, 3) // [0, 0, 0] */ export const distribute = (value: CurrencyArguments, length: number) => { if (length <= 1) { diff --git a/src/utils/vue/effectDispose.ts b/src/utils/vue/effectDispose.ts index ac44b1cd..749b7196 100644 --- a/src/utils/vue/effectDispose.ts +++ b/src/utils/vue/effectDispose.ts @@ -17,7 +17,15 @@ import type { AnyFC } from '@/types' * * @param fc effect 作用域卸载时需执行函数 * - * @remark 返回 true 表示获取到 effect 作用域并且卸载;false 表示未存在 effect 作用域 + * @description + * 返回 true 表示获取到 effect 作用域并且卸载;false 表示未存在 effect 作用域 + * + * @example + * const watchStop = watch(() => {}, () => {}) + * const watchEffectStop = watchEffect(() => {}) + * + * effectDispose(watchStop) + * effectDispose(watchEffectStop) */ export function effectDispose(fc: T) { if (getCurrentScope()) { diff --git a/src/views/demo/directive/index.tsx b/src/views/demo/directive/index.tsx index ca5b71b6..4098b6c2 100644 --- a/src/views/demo/directive/index.tsx +++ b/src/views/demo/directive/index.tsx @@ -70,14 +70,14 @@ const RDirective = defineComponent({ v-throttle={{ func: this.updateDemoValue.bind(null, 'throttleBtnClickCount'), trigger: 'click', - wait: 1000, + wait: 3000, options: {}, }} > 点击执行

我执行了{this.throttleBtnClickCount}次

-

该方法 1s 内仅会执行一次

+

该方法 3s 内仅会执行一次

@@ -86,14 +86,14 @@ const RDirective = defineComponent({ v-debounce={{ func: this.updateDemoValue.bind(null, 'debounceBtnClickCount'), trigger: 'click', - wait: 1000, + wait: 3000, options: {}, }} > 点击执行

我执行了{this.debounceBtnClickCount}次

-

该方法将延迟 1s 执行

+

该方法将延迟 3s 执行

diff --git a/src/views/login/index.scss b/src/views/login/index.scss index ec5d0d0e..91db803f 100644 --- a/src/views/login/index.scss +++ b/src/views/login/index.scss @@ -4,6 +4,7 @@ $positionY: 24px; .login { width: 100%; display: flex; + overflow: hidden; & .login-wrapper { position: relative; diff --git a/tsconfig.json b/tsconfig.json index 70404a79..af56bc9b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "module": "ESNext", "moduleResolution": "bundler", "strict": true, + "newLine": "LF", "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true,