diff --git a/CHANGELOG.md b/CHANGELOG.md index 456b93d1..205394a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGE LOG +## 4.3.2 + +### Feats + +- `useMenuTag` 所有关闭方法,都支持了多种参数类型传递方式。并且 `closeRight`, `closeLeft` 方法能够正确的关闭标签页 +- `useDayjs` 新增一些常用方法 +- 新增 `appExpandRoutes` 变量,存放展开、提升后的路由 +- `scopeDispose` 重命名为 `effectDispose` + +### Fixes + +- 修复 LayoutContent 全屏时候在手机浏览器打开高度显示不正确问题 + ## 4.3.1 根据反馈,尽可能的补充了一些代码注释。 @@ -8,7 +21,7 @@ - 标签页右键菜单新增关闭当前页功能,优化了文案 - `utils/basic` 包中的部分方法改为 effect 执行逻辑,避免使用 ref 注册的 dom 不能正确的被获取的问题 -- 新增 `scopeDispose`, `watchEffectWithTarget` 方法 +- 新增 `effectDispose`, `watchEffectWithTarget` 方法 - `utils/cache` 新增 `hasStorage` 方法 - 现在标签页会缓存,不再随着刷新后丢失 - 新增 maximize 方法,并且基于该方法实现 LayoutContent 全屏效果 diff --git a/package.json b/package.json index 4f399009..ae478b33 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ray-template", "private": false, - "version": "4.3.1", + "version": "4.3.2", "type": "module", "engines": { "node": ">=16.0.0", diff --git a/src/app-components/app/RayLink/index.tsx b/src/app-components/app/RayLink/index.tsx index dc742a16..ca4f1702 100644 --- a/src/app-components/app/RayLink/index.tsx +++ b/src/app-components/app/RayLink/index.tsx @@ -15,13 +15,13 @@ const RayLink = defineComponent({ key: 'yunhome', src: 'https://yunkuangao.me/', tooltip: '云之家', - icon: 'https://yunkuangao.me/wp-content/uploads/2022/05/cropped-cropped-QQ%E5%9B%BE%E7%89%8720220511113928.jpg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/avatar.jpeg', }, { key: 'yun-cloud-images', src: 'https://yunkuangao.com/', tooltip: '云图床', - icon: 'https://yunkuangao.me/wp-content/uploads/2022/05/cropped-cropped-QQ%E5%9B%BE%E7%89%8720220511113928.jpg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/avatar.jpeg', }, { key: 'ray-js-note', diff --git a/src/global-variable/variable.ts b/src/global-variable/variable.ts index e0d20b50..b24206df 100644 --- a/src/global-variable/variable.ts +++ b/src/global-variable/variable.ts @@ -30,10 +30,10 @@ import type { AnyFC } from '@/types/modules/utils' const variableState = reactive({ - globalSpinning: false, - globalDrawerValue: false, - globalMainLayoutLoad: true, - layoutContentMaximize: false, + globalSpinning: false, // 全局加载控制器 + globalDrawerValue: false, // 全局抽屉控制器(小尺寸设备可用) + globalMainLayoutLoad: true, // LayoutContent 区域加载控制器 + layoutContentMaximize: false, // LayoutContent 区域全屏控制器 }) export type VariableState = typeof variableState diff --git a/src/hooks/template/useMainPage.ts b/src/hooks/template/useMainPage.ts index e29a66ac..4362e255 100644 --- a/src/hooks/template/useMainPage.ts +++ b/src/hooks/template/useMainPage.ts @@ -13,6 +13,7 @@ import { setVariable } from '@/global-variable/index' import { LAYOUT_CONTENT_REF } from '@/app-config/routerConfig' import { addStyle, removeStyle } from '@/utils/element' import { unrefElement } from '@/utils/vue/index' +import { useWindowSize } from '@vueuse/core' import type { Ref } from 'vue' @@ -39,13 +40,15 @@ export function useMainPage() { const contentEl = unrefElement(LAYOUT_CONTENT_REF as Ref) if (contentEl) { - const { left, top } = contentEl.getBoundingClientRect() + const { left, top } = contentEl.getBoundingClientRect() // 使用 left, top 计算 translate 偏移 + const { height } = useWindowSize() // 获取实际高度避免 100vh 会导致手机端浏览器获取不准确问题 full ? addStyle(contentEl, { transform: `translate(-${left}px, -${top}px)`, + height: `${height.value}px`, }) - : removeStyle(contentEl, ['transform']) + : removeStyle(contentEl, ['transform', 'height']) } setVariable('layoutContentMaximize', full) diff --git a/src/hooks/template/useMenuTag.ts b/src/hooks/template/useMenuTag.ts index b5491117..4b0aca08 100644 --- a/src/hooks/template/useMenuTag.ts +++ b/src/hooks/template/useMenuTag.ts @@ -28,62 +28,150 @@ export function useMenuTag() { /** * - * 如果为非 root path,直接导航至上一 menuTag + * @param target 标签页对象、索引、key + * @param fc 触发函数 + * + * 该方法用于统一获取目标标签页方法 */ - const navigationPreTagOption = () => { - const options = getMenuTagOptions.value - const length = options.length - const preOption = options[length - 1] + const normalMenuTagOption = (target: CloseMenuTag, fc: string) => { + if (typeof target === 'number') { + // 判断是否为 NaN + if (isNaN(target)) { + console.warn(`${fc}: The ${target} is NaN, expect number.`) - if (getMenuKey.value !== path) { - changeMenuModelValue(preOption.key as string, preOption) + return + } + + // 判断是否超出当前标签页列表最大长度或者是否为负数 + if (target > getMenuTagOptions.value.length || target < -1) { + console.warn( + `${fc}: The incoming index ${target} did not match the corresponding item.`, + ) + + return + } + + return { + option: getMenuTagOptions.value[target], + index: target, + } + } else if (typeof target === 'string') { + // 查找符合条件的 key + const index = getMenuTagOptions.value.findIndex( + (curr) => curr.key === target, + ) + + return index > -1 + ? { + option: getMenuTagOptions.value[index], + index, + } + : console.warn( + `${fc}: The incoming key ${target} did not match the corresponding item.`, + ) + } else { + const { key } = target + const index = getMenuTagOptions.value.findIndex( + (curr) => curr.key === key, + ) + + if (index === -1) { + console.warn( + `${fc}: The incoming menuTag option ${target.key} did not match the corresponding item.`, + ) + + return + } + + return { + option: target, + index, + } } } + /** + * + * @remark 获取当前激活标签页索引位置 + */ + const getCurrentTagIndex = () => { + return getMenuTagOptions.value.findIndex( + (curr) => curr.key === getMenuKey.value, + ) + } + /** * * @param target 当前关闭项 * * 传递参数类型情况: * - number: 关闭当前项索引 - * - string: 关闭当前项 key + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 + * - AppMenuOption: 关闭当前项 + * + * @remark 校验指定标签右侧是否有可关闭的标签 + */ + const checkCloseRight = (target: CloseMenuTag) => { + const normal = normalMenuTagOption(target, 'checkCloseRight') + + if (normal) { + const { index } = normal + const length = getMenuTagOptions.value.length - 1 + + return !(index >= length) + } + + return false + } + + /** + * + * @param target 当前关闭项 + * + * 传递参数类型情况: + * - number: 关闭当前项索引 + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 + * - AppMenuOption: 关闭当前项 + * + * @remark 校验指定标签左侧是否有可关闭的标签 + */ + const checkCloseLeft = (target: CloseMenuTag) => { + const normal = normalMenuTagOption(target, 'checkCloseRight') + + if (normal) { + const { index } = normal + const length = getMenuTagOptions.value.length - 1 + + if (index === 0) { + return false + } + + if (index > 0 && length > 0) { + return true + } + + return false + } + + return false + } + + /** + * + * @param target 当前关闭项 + * + * 传递参数类型情况: + * - number: 关闭当前项索引 + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 * - AppMenuOption: 关闭当前项 */ const close = (target: CloseMenuTag) => { - if (typeof target === 'number') { - if (isNaN(target)) { - console.warn(`close: The ${target} is NaN, expect number.`) + const normal = normalMenuTagOption(target, 'close') - return - } + if (normal) { + const { option } = normal - if (target > getMenuTagOptions.value.length) { - console.warn( - `close: The ${target} is greater than menuTagOptions length.`, - ) - - return - } - - spliceMenTagOptions(target) - navigationPreTagOption() - } else if (typeof target === 'string') { - const findOptionIndex = getMenuTagOptions.value.findIndex( - (curr) => curr.key === target, - ) - - if (findOptionIndex !== -1) { - spliceMenTagOptions(findOptionIndex) - navigationPreTagOption() - } else { - console.warn( - `close: The ${target} is not found in current menuTagOptions.`, - ) - - return - } - } else { - changeMenuModelValue(target.key as string, target) + changeMenuModelValue(option.key, option) } } @@ -103,59 +191,86 @@ export function useMenuTag() { /** * - * @param currentIndex 开始关闭索引 + * @param target 目标标签页 * * 关闭以当前项为索引的右侧标签 - * 如果当前选择标签与 menuKey 不匹配,则会关闭当前标签右侧所有变迁并且跳转至该页面 + * 如果当前选择标签与 menuKey 不匹配并且包含了当前激活标签页,则会关闭当前标签右侧所有变迁并且跳转至该页面 + * + * 传递参数类型情况: + * - number: 关闭当前项索引 + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 + * - AppMenuOption: 关闭当前项 */ - const closeRight = (currentIndex: number) => { - const spliceLength = getMenuTagOptions.value.length - currentIndex - const routeOption = getMenuTagOptions.value[currentIndex] + const closeRight = (target: CloseMenuTag) => { + const normal = normalMenuTagOption(target, 'closeRight') - if (spliceLength > -1 && routeOption) { - spliceMenTagOptions(currentIndex + 1, spliceLength) + if (normal) { + const { option, index } = normal + const spliceLength = getMenuTagOptions.value.length - index // 待删除长度 + const currentIndex = getCurrentTagIndex() - if (getMenuKey.value !== routeOption.key) { - changeMenuModelValue(routeOption.key as string, routeOption) + spliceMenTagOptions(index + 1, spliceLength) + + if (index <= currentIndex) { + if (getMenuKey.value !== option.key) { + changeMenuModelValue(option.key as string, option) + } } - } else { - console.warn( - `closeRight: The ${currentIndex} is not found in current menuTagOptions.`, - ) } } /** * - * @param currentIndex 当前选择项的索引 + * @param target 目标标签页 * * 关闭以当前项左侧所有标签 - * 如果当前选择标签与 menuKey 不匹配,则会关闭当前标签左侧所有变迁并且跳转至该页面 + * 如果当前选择标签与 menuKey 不匹配并且包含了当前激活标签页,则会关闭当前标签左侧所有变迁并且跳转至该页面 + * + * 传递参数类型情况: + * - number: 关闭当前项索引 + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 + * - AppMenuOption: 关闭当前项 */ - const closeLeft = (currentIndex: number) => { - spliceMenTagOptions(0, currentIndex) + const closeLeft = (target: CloseMenuTag) => { + const normal = normalMenuTagOption(target, 'closeLeft') + + if (normal) { + const { option, index } = normal + const currentIndex = getCurrentTagIndex() + + spliceMenTagOptions(0, index) + + if (currentIndex <= index) { + if (getMenuKey.value !== option.key) { + changeMenuModelValue(option.key as string, option) + } + } + } } /** * - * @param currentIndex 当前项索引 + * @param target 目标标签页 * * 会关闭除了当前索引的所有菜单项 + * + * 传递参数类型情况: + * - number: 关闭当前项索引 + * - string: 关闭当前项 key,其实 key 也是一个具体的页面 url 地址 + * - AppMenuOption: 关闭当前项 */ - const closeOther = (currentIndex: number) => { - const routeOption = getMenuTagOptions.value[currentIndex] + const closeOther = (target: CloseMenuTag) => { + const normal = normalMenuTagOption(target, 'closeOther') - if (routeOption) { - if (getMenuKey.value !== routeOption.key) { + if (normal) { + const { option } = normal + + if (getMenuKey.value !== option.key) { emptyMenuTagOptions() - changeMenuModelValue(routeOption.key, routeOption) + changeMenuModelValue(option.key, option) } else { - setMenuTagOptions(routeOption, false) + setMenuTagOptions(option, false) } - } else { - console.warn( - `closeOther: The ${currentIndex} is not found in current menuTagOptions.`, - ) } } @@ -165,5 +280,8 @@ export function useMenuTag() { closeRight, closeLeft, closeOther, + getCurrentTagIndex, + checkCloseRight, + checkCloseLeft, } } diff --git a/src/hooks/web/useDayjs.ts b/src/hooks/web/useDayjs.ts index 1e2d92ec..146ae610 100644 --- a/src/hooks/web/useDayjs.ts +++ b/src/hooks/web/useDayjs.ts @@ -14,6 +14,17 @@ import { DEFAULT_DAYJS_LOCAL, DAYJS_LOCAL_MAP } from '@/app-config/localConfig' import type { DayjsLocal } from '@/dayjs/type' +export interface FormatOption { + format?: string +} + +export interface DateRange { + start: dayjs.ConfigType + end: dayjs.ConfigType +} + +const defaultDayjsFormat = 'YYYY-MM-DD HH:mm:ss' + /** * * dayjs hook @@ -34,7 +45,113 @@ export const useDayjs = () => { locale ? dayjs.locale(locale) : dayjs.locale(DEFAULT_DAYJS_LOCAL) } + /** + * + * @param d 待校验参数 + * + * @remark 校验是否为 dayjs 对象 + * + * @example + * isDayjs('2022-11-11') => false + * isDayjs('1699687245973) => false + * isDayjs(dayjs()) => true + */ + const isDayjs = (d: unknown) => { + return dayjs.isDayjs(d) + } + + /** + * + * @param date 待格式化参数 + * @param formatOption 格式化配置项 + * + * @remark 格式化日期 + * + * @example + * dayjs().format() => '2020-04-02T08:02:17-05:00' + * dayjs('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]') => 'YYYYescape 2019-01-25T00:00:00-02:00Z' + * dayjs('2019-01-25').format('DD/MM/YYYY') => '25/01/2019' + */ + const format = (date: dayjs.ConfigType, formatOption?: FormatOption) => { + const { format = defaultDayjsFormat } = formatOption ?? {} + + return dayjs(date).format(format) + } + + /** + * + * @param formatOption 格式化配置项 + * + * @remark 获取当前日期的开始和结束时间 + * + * 会返回当前日期、当前日期的开始时间和结束时间(未格式化与格式化后的) + */ + const getStartAndEndOfDay = (formatOption?: FormatOption) => { + const { format = defaultDayjsFormat } = formatOption ?? {} + const today = dayjs() + const startOfDay = today.startOf('day') + const endOfDay = today.endOf('day') + const formatToday = today.format(format) + const formatStartOfDay = startOfDay.format(format) + + return { + today, + startOfDay, + endOfDay, + formatToday, + formatStartOfDay, + } as const + } + + /** + * + * @param date1 待计算日期 + * @param date2 待计算日期 + * + * @remark 计算两天日期天数差异 + * + * 返回正数: date2 比 date1 晚 + * 返回负数: date2 比 date1 早 + * 返回零(0): date2 等于 date1 + * + * @example + * daysDiff('2022-01-11', '2022-01-12') => 1 + * daysDiff('2021-01-11', '2022-01-12') => 366 + * daysDiff('2023-01-11', '2022-01-12') => -364 + */ + const daysDiff = (date1: dayjs.ConfigType, date2: dayjs.ConfigType) => { + const start = dayjs(date1) + const end = dayjs(date2) + + return end.diff(start, 'days') + } + + /** + * + * @param date 待比较时间 + * @param range 待比较开始与结束时间 + * + * 判断一个时间是否在 start date 与 end date 之间 + * 如果刚还是等于开始、结束时间,也会返回 false + * + * @example + * isDateInRange('2023-01-16', { start: '2023-01-15', end: '2023-01-20' }) => true + * isDateInRange('2023-01-15', { start: '2023-01-15', end: '2023-01-20' }) => false + * isDateInRange('2023-01-20', { start: '2023-01-15', end: '2023-01-20' }) => false + */ + const isDateInRange = (date: dayjs.ConfigType, range: DateRange): boolean => { + const { start, end } = range + const currentDate = dayjs(date) + + return currentDate.isAfter(start) && currentDate.isBefore(end) + } + return { locale, + getStartAndEndOfDay, + format, + isDayjs, + daysDiff, + isDateInRange, } } diff --git a/src/layout/components/MenuTag/index.scss b/src/layout/components/MenuTag/index.scss index aac97f18..7ab8176b 100644 --- a/src/layout/components/MenuTag/index.scss +++ b/src/layout/components/MenuTag/index.scss @@ -25,9 +25,6 @@ $menuTagWrapperWidth: 76px; } & .menu-tag__right-wrapper { - // display: inline-flex; - // align-items: center; - & .menu-tag__right-arrow { transform: rotate(270deg); } diff --git a/src/layout/index.scss b/src/layout/index.scss index 25b648dd..c3468cb3 100644 --- a/src/layout/index.scss +++ b/src/layout/index.scss @@ -14,8 +14,6 @@ &.r-layout-full__viewer-content--maximize { position: fixed; width: 100%; - height: 100vh; - transform-origin: center; z-index: 99; } diff --git a/src/router/appRouteModules.ts b/src/router/appRouteModules.ts index 5662918c..be151799 100644 --- a/src/router/appRouteModules.ts +++ b/src/router/appRouteModules.ts @@ -22,6 +22,10 @@ import { combineRawRouteModules } from '@/router/helper/setupHelper' import { orderRoutes } from '@/router/helper/setupHelper' +import { expandRoutes } from '@/router/helper/expandRoutes' /** 获取所有被合并与排序的路由 */ export const getAppRawRoutes = () => orderRoutes(combineRawRouteModules()) + +/** 获取所有平铺展开的路由 */ +export const appExpandRoutes = expandRoutes(getAppRawRoutes()) diff --git a/src/router/routes.ts b/src/router/routes.ts index c3351699..cf9640dd 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,7 +1,6 @@ import Layout from '@/layout/index' -import { getAppRawRoutes } from './appRouteModules' +import { appExpandRoutes } from './appRouteModules' import { ROOT_ROUTE } from '@/app-config/appConfig' -import { expandRoutes } from '@/router/helper/expandRoutes' export default async () => [ /** @@ -23,6 +22,6 @@ export default async () => [ name: 'layout', redirect: ROOT_ROUTE.path, component: Layout, - children: expandRoutes(getAppRawRoutes()), + children: appExpandRoutes, }, ] diff --git a/src/utils/basic.ts b/src/utils/basic.ts index 860f462a..b3233fb1 100644 --- a/src/utils/basic.ts +++ b/src/utils/basic.ts @@ -133,7 +133,7 @@ export const downloadAnyFile = ( } else if (data instanceof File || data instanceof Blob) { blobData = data } else { - reject(new Error('Unsupported data type')) + reject(new Error('downloadAnyFile: Unsupported data type.')) return } diff --git a/src/utils/vue/scopeDispose.ts b/src/utils/vue/effectDispose.ts similarity index 92% rename from src/utils/vue/scopeDispose.ts rename to src/utils/vue/effectDispose.ts index 3f983624..a5a0be33 100644 --- a/src/utils/vue/scopeDispose.ts +++ b/src/utils/vue/effectDispose.ts @@ -19,7 +19,7 @@ import type { AnyFC } from '@/types/modules/utils' * * @remark 返回 true 表示获取到 effect 作用域并且卸载;false 表示未存在 effect 作用域 */ -export function scopeDispose(fc: AnyFC) { +export function effectDispose(fc: AnyFC) { if (getCurrentScope()) { onScopeDispose(fc) diff --git a/src/utils/vue/index.ts b/src/utils/vue/index.ts index 74b000ea..c47074de 100644 --- a/src/utils/vue/index.ts +++ b/src/utils/vue/index.ts @@ -1,5 +1,5 @@ export { call } from './call' export { unrefElement } from './unrefElement' export { renderNode } from './renderNode' -export { scopeDispose } from './scopeDispose' +export { effectDispose } from './effectDispose' export { watchEffectWithTarget } from './watchEffectWithTarget' diff --git a/src/utils/vue/watchEffectWithTarget.ts b/src/utils/vue/watchEffectWithTarget.ts index b78d31dc..5bcc43cf 100644 --- a/src/utils/vue/watchEffectWithTarget.ts +++ b/src/utils/vue/watchEffectWithTarget.ts @@ -9,7 +9,7 @@ * @remark 今天也是元气满满撸代码的一天 */ -import { scopeDispose } from './scopeDispose' +import { effectDispose } from './effectDispose' import type { WatchOptionsBase } from 'vue' import type { AnyFC } from '@/types/modules/utils' @@ -28,5 +28,5 @@ export function watchEffectWithTarget( ) { const stop = watchEffect(fc, watchOptions) - scopeDispose(stop) + effectDispose(stop) } diff --git a/src/views/demo/template-hooks/index.tsx b/src/views/demo/template-hooks/index.tsx index 758363c9..1d0e72db 100644 --- a/src/views/demo/template-hooks/index.tsx +++ b/src/views/demo/template-hooks/index.tsx @@ -12,12 +12,13 @@ import { NSpace, NCard, NButton } from 'naive-ui' import { useAppMenu, useMainPage } from '@/hooks/template/index' +import { getVariableToRefs } from '@/global-variable/index' export default defineComponent({ name: 'TemplateHooks', setup() { const currentMenuOption = ref('') - const maximizeRef = ref(false) + const maximizeRef = getVariableToRefs('layoutContentMaximize') const { navigationTo } = useAppMenu() const { reload, maximize } = useMainPage() @@ -67,9 +68,7 @@ export default defineComponent({ { - this.maximizeRef = !this.maximizeRef - - maximize(this.maximizeRef) + maximize(!this.maximizeRef) }} > 最大化内容区域