diff --git a/src/App.vue b/src/App.vue index 2134863..471ef60 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,6 +9,7 @@ const { initGreyAndColorWeakness } = useGreyAndColorWeakness() // 初始化主题 initTheme() + // 初始化灰色模式和色弱模式 initGreyAndColorWeakness() diff --git a/src/assets/styles/element-plus.css b/src/assets/styles/element-plus.css index be36db2..c550ac9 100644 --- a/src/assets/styles/element-plus.css +++ b/src/assets/styles/element-plus.css @@ -1,7 +1,7 @@ /** - * dark-blue 主题模式下的 Element Plus CSS 变量 - * 在此查阅所有可自定义的变量:https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss - * 也可以打开浏览器控制台选择元素,查看要覆盖的变量名 + * @description dark-blue 主题模式下的 Element Plus CSS 变量 + * @description 在此查阅所有可自定义的变量:https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss + * @description 也可以打开浏览器控制台选择元素,查看要覆盖的变量名 */ /* 基础颜色 */ diff --git a/src/assets/styles/vxe-table.css b/src/assets/styles/vxe-table.css index 8282936..37743c6 100644 --- a/src/assets/styles/vxe-table.css +++ b/src/assets/styles/vxe-table.css @@ -1,7 +1,7 @@ /** - * 所有主题模式下的 Vxe Table CSS 变量 - * 用 Element Plus 的 CSS 变量来覆写 Vxe Table 的 CSS 变量,目的是使 Vxe Table 支持多主题模式且样式统一 - * 在此查阅所有可自定义的变量:https://github.com/x-extends/vxe-table/blob/master/styles/css-variable.scss + * @description 所有主题模式下的 Vxe Table CSS 变量 + * @description 用 Element Plus 的 CSS 变量来覆写 Vxe Table 的 CSS 变量,目的是使 Vxe Table 支持多主题模式且样式统一 + * @description 在此查阅所有可自定义的变量:https://github.com/x-extends/vxe-table/blob/master/styles/css-variable.scss */ :root { diff --git a/src/components/Notify/index.vue b/src/components/Notify/index.vue index bc0df28..adb37b7 100644 --- a/src/components/Notify/index.vue +++ b/src/components/Notify/index.vue @@ -16,12 +16,16 @@ interface DataItem { /** 角标当前值 */ const badgeValue = computed(() => data.value.reduce((sum, item) => sum + item.list.length, 0)) + /** 角标最大值 */ const badgeMax = 99 + /** 面板宽度 */ const popoverWidth = 350 + /** 当前 Tab */ const activeName = ref("通知") + /** 所有数据 */ const data = ref([ // 通知数据 diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue index a02329d..96ad5d4 100644 --- a/src/components/Screenfull/index.vue +++ b/src/components/Screenfull/index.vue @@ -22,7 +22,9 @@ const props = withDefaults(defineProps(), { }) const CONTENT_LARGE = "content-large" + const CONTENT_FULL = "content-full" + const classList = document.body.classList // #region 全屏 @@ -35,17 +37,21 @@ function handleFullscreenClick() { const dom = document.querySelector(props.element) || undefined isEnabled ? screenfull.toggle(dom) : ElMessage.warning("您的浏览器无法工作") } + function handleFullscreenChange() { isFullscreen.value = screenfull.isFullscreen // 退出全屏时清除相关的 class isFullscreen.value || classList.remove(CONTENT_LARGE, CONTENT_FULL) } + watchEffect((onCleanup) => { if (isEnabled) { // 挂载组件时自动执行 screenfull.on("change", handleFullscreenChange) // 卸载组件时自动执行 - onCleanup(() => screenfull.off("change", handleFullscreenChange)) + onCleanup(() => { + screenfull.off("change", handleFullscreenChange) + }) } }) // #endregion @@ -54,11 +60,13 @@ watchEffect((onCleanup) => { const isContentLarge = ref(false) const contentLargeTips = computed(() => (isContentLarge.value ? "内容区复原" : "内容区放大")) const contentLargeSvgName = computed(() => (isContentLarge.value ? "fullscreen-exit" : "fullscreen")) + function handleContentLargeClick() { isContentLarge.value = !isContentLarge.value // 内容区放大时,将不需要的组件隐藏 classList.toggle(CONTENT_LARGE, isContentLarge.value) } + function handleContentFullClick() { // 取消内容区放大 isContentLarge.value && handleContentLargeClick() diff --git a/src/components/SearchMenu/Modal.vue b/src/components/SearchMenu/Modal.vue index 9db9527..1fa15d7 100644 --- a/src/components/SearchMenu/Modal.vue +++ b/src/components/SearchMenu/Modal.vue @@ -130,7 +130,7 @@ function handleEnter() { try { router.push({ name }) } catch { - return ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入") + return ElMessage.warning("该菜单有必填的动态参数,无法通过搜索进入") } handleClose() } diff --git a/src/components/SearchMenu/Result.vue b/src/components/SearchMenu/Result.vue index 00fb3b3..e67c249 100644 --- a/src/components/SearchMenu/Result.vue +++ b/src/components/SearchMenu/Result.vue @@ -8,10 +8,12 @@ interface Props { } const props = defineProps() + /** 选中的菜单 */ const modelValue = defineModel({ required: true }) const instance = getCurrentInstance() + const scrollbarHeight = ref(0) /** 菜单的样式 */ @@ -47,13 +49,19 @@ function getScrollTop(index: number) { } // 在组件挂载前添加窗口大小变化事件监听器 -onBeforeMount(() => window.addEventListener("resize", getScrollbarHeight)) +onBeforeMount(() => { + window.addEventListener("resize", getScrollbarHeight) +}) // 在组件挂载时立即计算滚动可视区高度 -onMounted(() => getScrollbarHeight()) +onMounted(() => { + getScrollbarHeight() +}) // 在组件卸载前移除窗口大小变化事件监听器 -onBeforeUnmount(() => window.removeEventListener("resize", getScrollbarHeight)) +onBeforeUnmount(() => { + window.removeEventListener("resize", getScrollbarHeight) +}) defineExpose({ getScrollTop }) diff --git a/src/components/ThemeSwitch/index.vue b/src/components/ThemeSwitch/index.vue index ce5dcbb..1c65d00 100644 --- a/src/components/ThemeSwitch/index.vue +++ b/src/components/ThemeSwitch/index.vue @@ -14,7 +14,9 @@ function handleChangeTheme({ clientX, clientY }: MouseEvent, themeName: ThemeNam style.setProperty("--v3-theme-x", `${clientX}px`) style.setProperty("--v3-theme-y", `${clientY}px`) style.setProperty("--v3-theme-r", `${maxRadius}px`) - const handler = () => setTheme(themeName) + const handler = () => { + setTheme(themeName) + } document.startViewTransition ? document.startViewTransition(handler) : handler() } diff --git a/src/composables/useDevice.ts b/src/composables/useDevice.ts index 62de52d..fb7c7dc 100644 --- a/src/composables/useDevice.ts +++ b/src/composables/useDevice.ts @@ -3,6 +3,7 @@ import { useAppStore } from "@/pinia/stores/app" import { computed } from "vue" const appStore = useAppStore() + const isMobile = computed(() => appStore.device === DeviceEnum.Mobile) const isDesktop = computed(() => appStore.device === DeviceEnum.Desktop) diff --git a/src/composables/useFetchSelect.ts b/src/composables/useFetchSelect.ts index 000954f..16917ca 100644 --- a/src/composables/useFetchSelect.ts +++ b/src/composables/useFetchSelect.ts @@ -29,16 +29,16 @@ export function useFetchSelect(props: FetchSelectProps) { const loadData = () => { loading.value = true options.value = [] - api() - .then((res) => { - options.value = res.data - }) - .finally(() => { - loading.value = false - }) + api().then((res) => { + options.value = res.data + }).finally(() => { + loading.value = false + }) } - onMounted(() => loadData()) + onMounted(() => { + loadData() + }) return { loading, options, value } } diff --git a/src/composables/useGreyAndColorWeakness.ts b/src/composables/useGreyAndColorWeakness.ts index 0262097..52db81e 100644 --- a/src/composables/useGreyAndColorWeakness.ts +++ b/src/composables/useGreyAndColorWeakness.ts @@ -3,6 +3,7 @@ import { watchEffect } from "vue" const GREY_MODE = "grey-mode" const COLOR_WEAKNESS = "color-weakness" + const classList = document.documentElement.classList /** 初始化 */ diff --git a/src/composables/useLayoutMode.ts b/src/composables/useLayoutMode.ts index 06b2ddf..e9e0477 100644 --- a/src/composables/useLayoutMode.ts +++ b/src/composables/useLayoutMode.ts @@ -3,6 +3,7 @@ import { useSettingsStore } from "@/pinia/stores/settings" import { computed } from "vue" const settingsStore = useSettingsStore() + const isLeft = computed(() => settingsStore.layoutMode === LayoutModeEnum.Left) const isTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.Top) const isLeftTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.LeftTop) diff --git a/src/composables/useRouteListener.ts b/src/composables/useRouteListener.ts index 28fe60e..646fac0 100644 --- a/src/composables/useRouteListener.ts +++ b/src/composables/useRouteListener.ts @@ -6,7 +6,9 @@ import { onBeforeUnmount } from "vue" type Callback = (route: RouteLocationNormalized) => void const emitter = mitt() + const key = Symbol("ROUTE_CHANGE") + let latestRoute: RouteLocationNormalized /** 设置最新的路由信息,触发路由变化事件 */ @@ -18,9 +20,9 @@ export function setRouteChange(to: RouteLocationNormalized) { } /** - * 订阅路由变化 Composable - * 1. 单独用 watch 监听路由会浪费渲染性能 - * 2. 可优先选择使用该发布订阅模式去进行分发管理 + * @name 订阅路由变化 Composable + * @description 1. 单独用 watch 监听路由会浪费渲染性能 + * @description 2. 可优先选择使用该发布订阅模式去进行分发管理 */ export function useRouteListener() { // 回调函数集合 @@ -43,9 +45,7 @@ export function useRouteListener() { // 组件销毁前移除监听器 onBeforeUnmount(() => { - for (let i = 0; i < callbackList.length; i++) { - removeRouteListener(callbackList[i]) - } + callbackList.forEach(removeRouteListener) }) return { listenerRouteChange, removeRouteListener } diff --git a/src/composables/useWatermark.ts b/src/composables/useWatermark.ts index 18376a8..6021365 100644 --- a/src/composables/useWatermark.ts +++ b/src/composables/useWatermark.ts @@ -2,14 +2,6 @@ import type { Ref } from "vue" import { debounce } from "lodash-es" import { onBeforeUnmount, ref } from "vue" -interface Observer { - watermarkElMutationObserver?: MutationObserver - parentElMutationObserver?: MutationObserver - parentElResizeObserver?: ResizeObserver -} - -type DefaultConfig = typeof DEFAULT_CONFIG - /** 默认配置 */ const DEFAULT_CONFIG = { /** 防御(默认开启,能防御水印被删除或隐藏,但可能会有性能损耗) */ @@ -30,13 +22,21 @@ const DEFAULT_CONFIG = { height: 200 } +type DefaultConfig = typeof DEFAULT_CONFIG + +interface Observer { + watermarkElMutationObserver?: MutationObserver + parentElMutationObserver?: MutationObserver + parentElResizeObserver?: ResizeObserver +} + /** body 元素 */ const bodyEl = ref(document.body) /** - * 水印 Composable - * 1. 可以选择传入挂载水印的容器元素,默认是 body - * 2. 做了水印防御,能有效防御别人打开控制台删除或隐藏水印 + * @name 水印 Composable + * @description 1. 可以选择传入挂载水印的容器元素,默认是 body + * @description 2. 做了水印防御,能有效防御别人打开控制台删除或隐藏水印 */ export function useWatermark(parentEl: Ref = bodyEl) { // 备份文本 @@ -226,7 +226,9 @@ export function useWatermark(parentEl: Ref = bodyEl) { } // 在组件卸载前移除水印以及各种监听 - onBeforeUnmount(() => clearWatermark()) + onBeforeUnmount(() => { + clearWatermark() + }) return { setWatermark, clearWatermark } } diff --git a/src/config/route.ts b/src/config/route.ts index 6057b26..ad77af3 100644 --- a/src/config/route.ts +++ b/src/config/route.ts @@ -1,21 +1,22 @@ /** 路由配置 */ interface RouteSettings { /** - * 是否开启动态路由功能? - * 1. 开启后需要后端配合,在查询用户详情接口返回当前用户可以用来判断并加载动态路由的字段(该项目用的是角色 roles 字段) - * 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 dynamic: false + * @name 是否开启动态路由功能 + * @description 1. 开启后需要后端配合,在查询用户详情接口返回当前用户可以用来判断并加载动态路由的字段(该项目用的是角色 roles 字段) + * @description 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 dynamic: false */ dynamic: boolean /** - * 当动态路由功能关闭时: - * 1. 应该将所有路由都写到常驻路由里面(表明所有登录的用户能访问的页面都是一样的) - * 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色 + * @name 默认角色 + * @description 当动态路由功能关闭时: + * @description 1. 应该将所有路由都写到常驻路由里面(表明所有登录的用户能访问的页面都是一样的) + * @description 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色 */ defaultRoles: Array /** - * 是否开启三级及其以上路由缓存功能? - * 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由) - * 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效 + * @name 是否开启三级及其以上路由缓存功能 + * @description 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由) + * @description 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效 */ thirdLevelRouteCache: boolean } diff --git a/src/constants/app-key.ts b/src/constants/app-key.ts index 74f943f..cd5d7b9 100644 --- a/src/constants/app-key.ts +++ b/src/constants/app-key.ts @@ -13,8 +13,10 @@ export enum LayoutModeEnum { /** 侧边栏打开状态常量 */ export const SIDEBAR_OPENED = "opened" + /** 侧边栏关闭状态常量 */ export const SIDEBAR_CLOSED = "closed" export type SidebarOpened = typeof SIDEBAR_OPENED + export type SidebarClosed = typeof SIDEBAR_CLOSED diff --git a/src/directives/permission/index.ts b/src/directives/permission/index.ts index cce30ab..e858b75 100644 --- a/src/directives/permission/index.ts +++ b/src/directives/permission/index.ts @@ -2,7 +2,10 @@ import type { Directive } from "vue" import { useUserStore } from "@/pinia/stores/user" import { isArray } from "@/utils/validate" -/** 权限指令,和权限判断函数 checkPermission 功能类似 */ +/** + * @name 权限指令 + * @description 和权限判断函数 checkPermission 功能类似 + */ export const permission: Directive = { mounted(el, binding) { const { value: permissionRoles } = binding diff --git a/src/http/request.ts b/src/http/request.ts index d5365f4..c1b2c38 100644 --- a/src/http/request.ts +++ b/src/http/request.ts @@ -88,8 +88,6 @@ function createInstance() { case 505: error.message = "HTTP 版本不受支持" break - default: - break } ElMessage.error(error.message) return Promise.reject(error) diff --git a/src/layouts/components/Breadcrumb/index.vue b/src/layouts/components/Breadcrumb/index.vue index 236fd38..f439467 100644 --- a/src/layouts/components/Breadcrumb/index.vue +++ b/src/layouts/components/Breadcrumb/index.vue @@ -6,7 +6,9 @@ import { ref } from "vue" import { useRoute, useRouter } from "vue-router" const route = useRoute() + const router = useRouter() + const { listenerRouteChange } = useRouteListener() /** 定义响应式数据 breadcrumbs,用于存储面包屑导航信息 */ @@ -26,10 +28,7 @@ function pathCompile(path: string) { /** 处理面包屑导航点击事件 */ function handleLink(item: RouteLocationMatched) { const { redirect, path } = item - if (redirect) { - router.push(redirect as string) - return - } + if (redirect) return router.push(redirect as string) router.push(pathCompile(path)) } diff --git a/src/layouts/components/RightPanel/index.vue b/src/layouts/components/RightPanel/index.vue index aa495bb..ff854cd 100644 --- a/src/layouts/components/RightPanel/index.vue +++ b/src/layouts/components/RightPanel/index.vue @@ -11,6 +11,7 @@ const props = withDefaults(defineProps(), { }) const buttonTopCss = `${props.buttonTop}px` + const show = ref(false) diff --git a/src/layouts/components/Settings/index.vue b/src/layouts/components/Settings/index.vue index e64e934..1dfbe39 100644 --- a/src/layouts/components/Settings/index.vue +++ b/src/layouts/components/Settings/index.vue @@ -8,6 +8,7 @@ import { watchEffect } from "vue" import SelectLayoutMode from "./SelectLayoutMode.vue" const { isLeft } = useLayoutMode() + const settingsStore = useSettingsStore() // 使用 storeToRefs 将提取的属性保持其响应性 diff --git a/src/layouts/components/Sidebar/SidebarItem.vue b/src/layouts/components/Sidebar/SidebarItem.vue index 4800899..4550df7 100644 --- a/src/layouts/components/Sidebar/SidebarItem.vue +++ b/src/layouts/components/Sidebar/SidebarItem.vue @@ -18,14 +18,10 @@ const props = withDefaults(defineProps(), { const alwaysShowRootMenu = computed(() => props.item.meta?.alwaysShow) /** 显示的子菜单 */ -const showingChildren = computed(() => { - return props.item.children?.filter(child => !child.meta?.hidden) ?? [] -}) +const showingChildren = computed(() => props.item.children?.filter(child => !child.meta?.hidden) ?? []) /** 显示的子菜单数量 */ -const showingChildNumber = computed(() => { - return showingChildren.value.length -}) +const showingChildNumber = computed(() => showingChildren.value.length) /** 唯一的子菜单项 */ const theOnlyOneChild = computed(() => { diff --git a/src/layouts/components/Sidebar/index.vue b/src/layouts/components/Sidebar/index.vue index bc1c980..e13e421 100644 --- a/src/layouts/components/Sidebar/index.vue +++ b/src/layouts/components/Sidebar/index.vue @@ -21,28 +21,16 @@ const appStore = useAppStore() const permissionStore = usePermissionStore() const settingsStore = useSettingsStore() -const activeMenu = computed(() => { - const { - meta: { activeMenu }, - path - } = route - return activeMenu || path -}) +const activeMenu = computed(() => route.meta.activeMenu || route.path) const noHiddenRoutes = computed(() => permissionStore.routes.filter(item => !item.meta?.hidden)) const isCollapse = computed(() => !appStore.sidebar.opened) const isLogo = computed(() => isLeft.value && settingsStore.showLogo) const backgroundColor = computed(() => (isLeft.value ? v3SidebarMenuBgColor : undefined)) const textColor = computed(() => (isLeft.value ? v3SidebarMenuTextColor : undefined)) const activeTextColor = computed(() => (isLeft.value ? v3SidebarMenuActiveTextColor : undefined)) -const sidebarMenuItemHeight = computed(() => { - return !isTop.value ? "var(--v3-sidebar-menu-item-height)" : "var(--v3-navigationbar-height)" -}) -const sidebarMenuHoverBgColor = computed(() => { - return !isTop.value ? "var(--v3-sidebar-menu-hover-bg-color)" : "transparent" -}) -const tipLineWidth = computed(() => { - return !isTop.value ? "2px" : "0px" -}) +const sidebarMenuItemHeight = computed(() => !isTop.value ? "var(--v3-sidebar-menu-item-height)" : "var(--v3-navigationbar-height)") +const sidebarMenuHoverBgColor = computed(() => !isTop.value ? "var(--v3-sidebar-menu-hover-bg-color)" : "transparent") +const tipLineWidth = computed(() => !isTop.value ? "2px" : "0px")