refactor: 统一代码风格 (if 语句 & 箭头函数)
This commit is contained in:
parent
9b359203fc
commit
0f5f16cf0f
@ -9,6 +9,7 @@ const { initGreyAndColorWeakness } = useGreyAndColorWeakness()
|
||||
|
||||
// 初始化主题
|
||||
initTheme()
|
||||
|
||||
// 初始化灰色模式和色弱模式
|
||||
initGreyAndColorWeakness()
|
||||
|
||||
|
@ -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 也可以打开浏览器控制台选择元素,查看要覆盖的变量名
|
||||
*/
|
||||
|
||||
/* 基础颜色 */
|
||||
|
@ -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 {
|
||||
|
@ -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<TabName>("通知")
|
||||
|
||||
/** 所有数据 */
|
||||
const data = ref<DataItem[]>([
|
||||
// 通知数据
|
||||
|
@ -22,7 +22,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
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<boolean>(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()
|
||||
|
@ -130,7 +130,7 @@ function handleEnter() {
|
||||
try {
|
||||
router.push({ name })
|
||||
} catch {
|
||||
return ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入")
|
||||
return ElMessage.warning("该菜单有必填的动态参数,无法通过搜索进入")
|
||||
}
|
||||
handleClose()
|
||||
}
|
||||
|
@ -8,10 +8,12 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
/** 选中的菜单 */
|
||||
const modelValue = defineModel<RouteRecordName | undefined>({ required: true })
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
const scrollbarHeight = ref<number>(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 })
|
||||
</script>
|
||||
|
@ -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()
|
||||
}
|
||||
</script>
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { watchEffect } from "vue"
|
||||
|
||||
const GREY_MODE = "grey-mode"
|
||||
const COLOR_WEAKNESS = "color-weakness"
|
||||
|
||||
const classList = document.documentElement.classList
|
||||
|
||||
/** 初始化 */
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
@ -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<HTMLElement>(document.body)
|
||||
|
||||
/**
|
||||
* 水印 Composable
|
||||
* 1. 可以选择传入挂载水印的容器元素,默认是 body
|
||||
* 2. 做了水印防御,能有效防御别人打开控制台删除或隐藏水印
|
||||
* @name 水印 Composable
|
||||
* @description 1. 可以选择传入挂载水印的容器元素,默认是 body
|
||||
* @description 2. 做了水印防御,能有效防御别人打开控制台删除或隐藏水印
|
||||
*/
|
||||
export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
|
||||
// 备份文本
|
||||
@ -226,7 +226,9 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
|
||||
}
|
||||
|
||||
// 在组件卸载前移除水印以及各种监听
|
||||
onBeforeUnmount(() => clearWatermark())
|
||||
onBeforeUnmount(() => {
|
||||
clearWatermark()
|
||||
})
|
||||
|
||||
return { setWatermark, clearWatermark }
|
||||
}
|
||||
|
@ -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<string>
|
||||
/**
|
||||
* 是否开启三级及其以上路由缓存功能?
|
||||
* 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由)
|
||||
* 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效
|
||||
* @name 是否开启三级及其以上路由缓存功能
|
||||
* @description 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由)
|
||||
* @description 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效
|
||||
*/
|
||||
thirdLevelRouteCache: boolean
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -88,8 +88,6 @@ function createInstance() {
|
||||
case 505:
|
||||
error.message = "HTTP 版本不受支持"
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
ElMessage.error(error.message)
|
||||
return Promise.reject(error)
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const buttonTopCss = `${props.buttonTop}px`
|
||||
|
||||
const show = ref(false)
|
||||
</script>
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { watchEffect } from "vue"
|
||||
import SelectLayoutMode from "./SelectLayoutMode.vue"
|
||||
|
||||
const { isLeft } = useLayoutMode()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
// 使用 storeToRefs 将提取的属性保持其响应性
|
||||
|
@ -18,14 +18,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
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(() => {
|
||||
|
@ -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")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -15,16 +15,20 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const { listenerRouteChange } = useRouteListener()
|
||||
|
||||
/** 滚动条组件元素的引用 */
|
||||
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
|
||||
|
||||
/** 滚动条内容元素的引用 */
|
||||
const scrollbarContentRef = ref<HTMLDivElement>()
|
||||
|
||||
/** 当前滚动条距离左边的距离 */
|
||||
let currentScrollLeft = 0
|
||||
|
||||
/** 每次滚动距离 */
|
||||
const translateDistance = 200
|
||||
|
||||
|
@ -11,9 +11,13 @@ import { useRoute, useRouter } from "vue-router"
|
||||
import ScrollPane from "./ScrollPane.vue"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { listenerRouteChange } = useRouteListener()
|
||||
|
||||
/** 标签页组件元素的引用数组 */
|
||||
@ -21,12 +25,16 @@ const tagRefs = ref<InstanceType<typeof RouterLink>[]>([])
|
||||
|
||||
/** 右键菜单的状态 */
|
||||
const visible = ref(false)
|
||||
|
||||
/** 右键菜单的 top 位置 */
|
||||
const top = ref(0)
|
||||
|
||||
/** 右键菜单的 left 位置 */
|
||||
const left = ref(0)
|
||||
|
||||
/** 当前正在右键操作的标签页 */
|
||||
const selectedTag = ref<TagView>({})
|
||||
|
||||
/** 固定的标签页 */
|
||||
let affixTags: TagView[] = []
|
||||
|
||||
|
@ -10,7 +10,7 @@ const MAX_MOBILE_WIDTH = 992
|
||||
* @name 浏览器宽度变化 Composable
|
||||
* @description 根据浏览器宽度变化,变换 Layout 布局
|
||||
*/
|
||||
export default () => {
|
||||
export function useResize() {
|
||||
const appStore = useAppStore()
|
||||
const { listenerRouteChange } = useRouteListener()
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { getCssVar, setCssVar } from "@/utils/css"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { watchEffect } from "vue"
|
||||
import { RightPanel, Settings } from "./components"
|
||||
import useResize from "./composables/useResize"
|
||||
import { useResize } from "./composables/useResize"
|
||||
import LeftMode from "./LeftMode.vue"
|
||||
import LeftTopMode from "./LeftTopMode.vue"
|
||||
import TopMode from "./TopMode.vue"
|
||||
|
@ -20,6 +20,7 @@ const app = createApp(App)
|
||||
|
||||
// 加载插件
|
||||
loadPlugins(app)
|
||||
|
||||
// 加载自定义指令
|
||||
loadDirectives(app)
|
||||
|
||||
|
@ -5,12 +5,14 @@ import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||
import { getLoginCodeApi } from "@/http/login"
|
||||
import { useUserStore } from "@/pinia/stores/user"
|
||||
import { Key, Loading, Lock, Picture, User } from "@element-plus/icons-vue"
|
||||
import { ElMessage } from "element-plus"
|
||||
import { reactive, ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import Owl from "./components/Owl.vue"
|
||||
import { useFocus } from "./composables/useFocus"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { isFocus, handleBlur, handleFocus } = useFocus()
|
||||
|
||||
/** 登录表单元素的引用 */
|
||||
@ -18,51 +20,57 @@ const loginFormRef = ref<FormInstance | null>(null)
|
||||
|
||||
/** 登录按钮 Loading */
|
||||
const loading = ref(false)
|
||||
|
||||
/** 验证码图片 URL */
|
||||
const codeUrl = ref("")
|
||||
|
||||
/** 登录表单数据 */
|
||||
const loginFormData: LoginRequestData = reactive({
|
||||
username: "admin",
|
||||
password: "12345678",
|
||||
code: ""
|
||||
})
|
||||
|
||||
/** 登录表单校验规则 */
|
||||
const loginFormRules: FormRules = {
|
||||
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
username: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
|
||||
],
|
||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
|
||||
code: [
|
||||
{ required: true, message: "请输入验证码", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
/** 登录逻辑 */
|
||||
|
||||
/** 登录 */
|
||||
function handleLogin() {
|
||||
loginFormRef.value?.validate((valid: boolean, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
useUserStore()
|
||||
.login(loginFormData)
|
||||
.then(() => {
|
||||
router.push({ path: "/" })
|
||||
})
|
||||
.catch(() => {
|
||||
createCode()
|
||||
loginFormData.password = ""
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
console.error("表单校验不通过", fields)
|
||||
loginFormRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
ElMessage.error("表单校验不通过")
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
useUserStore().login(loginFormData).then(() => {
|
||||
router.push({ path: "/" })
|
||||
}).catch(() => {
|
||||
createCode()
|
||||
loginFormData.password = ""
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 创建验证码 */
|
||||
function createCode() {
|
||||
// 先清空验证码的输入
|
||||
// 清空已输入的验证码
|
||||
loginFormData.code = ""
|
||||
// 获取验证码
|
||||
// 清空验证图片
|
||||
codeUrl.value = ""
|
||||
// 获取验证码图片
|
||||
getLoginCodeApi().then((res) => {
|
||||
codeUrl.value = res.data
|
||||
})
|
||||
|
@ -3,7 +3,9 @@ import { useUserStore } from "@/pinia/stores/user"
|
||||
import { ref, watch } from "vue"
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const switchRoles = ref(userStore.roles[0])
|
||||
|
||||
watch(switchRoles, (value) => {
|
||||
userStore.changeRoles(value)
|
||||
})
|
||||
|
@ -30,19 +30,20 @@ const formRules: FormRules<CreateOrUpdateTableRequestData> = {
|
||||
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
|
||||
}
|
||||
function handleCreateOrUpdate() {
|
||||
formRef.value?.validate((valid: boolean, fields) => {
|
||||
if (!valid) return console.error("表单校验不通过", fields)
|
||||
formRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
ElMessage.error("表单校验不通过")
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
const api = formData.value.id === undefined ? createTableDataApi : updateTableDataApi
|
||||
api(formData.value)
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功")
|
||||
dialogVisible.value = false
|
||||
getTableData()
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
api(formData.value).then(() => {
|
||||
ElMessage.success("操作成功")
|
||||
dialogVisible.value = false
|
||||
getTableData()
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
function resetForm() {
|
||||
@ -87,17 +88,14 @@ function getTableData() {
|
||||
size: paginationData.pageSize,
|
||||
username: searchData.username || undefined,
|
||||
phone: searchData.phone || undefined
|
||||
}).then(({ data }) => {
|
||||
paginationData.total = data.total
|
||||
tableData.value = data.list
|
||||
}).catch(() => {
|
||||
tableData.value = []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
.then(({ data }) => {
|
||||
paginationData.total = data.total
|
||||
tableData.value = data.list
|
||||
})
|
||||
.catch(() => {
|
||||
tableData.value = []
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
function handleSearch() {
|
||||
paginationData.currentPage === 1 ? getTableData() : (paginationData.currentPage = 1)
|
||||
|
@ -20,13 +20,16 @@ export const useAppStore = defineStore("app", () => {
|
||||
opened: getSidebarStatus() !== SIDEBAR_CLOSED,
|
||||
withoutAnimation: false
|
||||
})
|
||||
|
||||
// 设备类型
|
||||
const device = ref<DeviceEnum>(DeviceEnum.Desktop)
|
||||
|
||||
// 监听侧边栏 opened 状态
|
||||
watch(
|
||||
() => sidebar.opened,
|
||||
opened => handleSidebarStatus(opened)
|
||||
(opened) => {
|
||||
handleSidebarStatus(opened)
|
||||
}
|
||||
)
|
||||
|
||||
// 切换侧边栏
|
||||
@ -34,11 +37,13 @@ export const useAppStore = defineStore("app", () => {
|
||||
sidebar.opened = !sidebar.opened
|
||||
sidebar.withoutAnimation = withoutAnimation
|
||||
}
|
||||
|
||||
// 关闭侧边栏
|
||||
const closeSidebar = (withoutAnimation: boolean) => {
|
||||
sidebar.opened = false
|
||||
sidebar.withoutAnimation = withoutAnimation
|
||||
}
|
||||
|
||||
// 切换设备类型
|
||||
const toggleDevice = (value: DeviceEnum) => {
|
||||
device.value = value
|
||||
|
@ -28,6 +28,7 @@ function filterDynamicRoutes(routes: RouteRecordRaw[], roles: string[]) {
|
||||
export const usePermissionStore = defineStore("permission", () => {
|
||||
// 可访问的路由
|
||||
const routes = ref<RouteRecordRaw[]>([])
|
||||
|
||||
// 有访问权限的动态路由
|
||||
const addRoutes = ref<RouteRecordRaw[]>([])
|
||||
|
||||
|
@ -34,20 +34,26 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
||||
const addCachedView = (view: TagView) => {
|
||||
if (typeof view.name !== "string") return
|
||||
if (cachedViews.value.includes(view.name)) return
|
||||
if (view.meta?.keepAlive) cachedViews.value.push(view.name)
|
||||
if (view.meta?.keepAlive) {
|
||||
cachedViews.value.push(view.name)
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region del
|
||||
const delVisitedView = (view: TagView) => {
|
||||
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||
if (index !== -1) visitedViews.value.splice(index, 1)
|
||||
if (index !== -1) {
|
||||
visitedViews.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const delCachedView = (view: TagView) => {
|
||||
if (typeof view.name !== "string") return
|
||||
const index = cachedViews.value.indexOf(view.name)
|
||||
if (index !== -1) cachedViews.value.splice(index, 1)
|
||||
if (index !== -1) {
|
||||
cachedViews.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
|
@ -23,6 +23,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
setToken(data.token)
|
||||
token.value = data.token
|
||||
}
|
||||
|
||||
// 获取用户详情
|
||||
const getInfo = async () => {
|
||||
const { data } = await getUserInfoApi()
|
||||
@ -30,6 +31,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
|
||||
roles.value = data.roles?.length > 0 ? data.roles : routeSettings.defaultRoles
|
||||
}
|
||||
|
||||
// 模拟角色变化
|
||||
const changeRoles = async (role: string) => {
|
||||
const newToken = `token-${role}`
|
||||
@ -38,6 +40,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
// 用刷新页面代替重新登录
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
// 登出
|
||||
const logout = () => {
|
||||
removeToken()
|
||||
@ -46,12 +49,14 @@ export const useUserStore = defineStore("user", () => {
|
||||
resetRouter()
|
||||
_resetTagsView()
|
||||
}
|
||||
|
||||
// 重置 Token
|
||||
const resetToken = () => {
|
||||
removeToken()
|
||||
token.value = ""
|
||||
roles.value = []
|
||||
}
|
||||
|
||||
// 重置 Visited Views 和 Cached Views
|
||||
const _resetTagsView = () => {
|
||||
if (!settingsStore.cacheTagsView) {
|
||||
|
@ -20,10 +20,8 @@ export function flatMultiLevelRoutes(routes: RouteRecordRaw[]) {
|
||||
/** 判断路由层级是否大于 2 */
|
||||
function isMultipleRoute(route: RouteRecordRaw) {
|
||||
const children = route.children
|
||||
if (children?.length) {
|
||||
// 只要有一个子路由的 children 长度大于 0,就说明是三级及其以上路由
|
||||
return children.some(child => child.children?.length)
|
||||
}
|
||||
// 只要有一个子路由的 children 长度大于 0,就说明是三级及其以上路由
|
||||
if (children?.length) return children.some(child => child.children?.length)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
}
|
||||
|
||||
// 如果已经登录,并准备进入 Login 页面,则重定向到主页
|
||||
if (to.path === "/login") {
|
||||
return next({ path: "/" })
|
||||
}
|
||||
if (to.path === "/login") return next({ path: "/" })
|
||||
|
||||
// 如果用户已经获得其权限角色
|
||||
if (userStore.roles.length !== 0) return next()
|
||||
@ -47,7 +45,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
} catch (error) {
|
||||
// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面
|
||||
userStore.resetToken()
|
||||
ElMessage.error((error as Error).message || "路由守卫过程发生错误")
|
||||
ElMessage.error((error as Error).message || "路由守卫发生错误")
|
||||
next("/login")
|
||||
}
|
||||
})
|
||||
|
2
src/utils/cache/cookies.ts
vendored
2
src/utils/cache/cookies.ts
vendored
@ -6,9 +6,11 @@ import Cookies from "js-cookie"
|
||||
export function getToken() {
|
||||
return Cookies.get(CacheKey.TOKEN)
|
||||
}
|
||||
|
||||
export function setToken(token: string) {
|
||||
Cookies.set(CacheKey.TOKEN, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
Cookies.remove(CacheKey.TOKEN)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** 获取指定元素(默认全局)上的 CSS 变量的值 */
|
||||
export function getCssVar(varName: string, element: HTMLElement = document.documentElement) {
|
||||
if (!varName?.startsWith("--")) {
|
||||
console.warn("CSS 变量名应以 '--' 开头")
|
||||
console.error("CSS 变量名应以 '--' 开头")
|
||||
return ""
|
||||
}
|
||||
// 没有拿到值时,会返回空串
|
||||
@ -11,7 +11,7 @@ export function getCssVar(varName: string, element: HTMLElement = document.docum
|
||||
/** 设置指定元素(默认全局)上的 CSS 变量的值 */
|
||||
export function setCssVar(varName: string, value: string, element: HTMLElement = document.documentElement) {
|
||||
if (!varName?.startsWith("--")) {
|
||||
console.warn("CSS 变量名应以 '--' 开头")
|
||||
console.error("CSS 变量名应以 '--' 开头")
|
||||
return
|
||||
}
|
||||
element.style.setProperty(varName, value)
|
||||
|
Loading…
x
Reference in New Issue
Block a user