Selaa lähdekoodia

feat: 架构完善、网站标题、路由守卫

zhengnaiwen_citu 1 vuosi sitten
vanhempi
commit
f5c4a7b85a
8 muutettua tiedostoa jossa 246 lisäystä ja 1 poistoa
  1. 1 0
      package.json
  2. 8 0
      pnpm-lock.yaml
  3. 33 0
      src/hooks/web/useNProgress.js
  4. 25 0
      src/hooks/web/useTitle.js
  5. 2 1
      src/main.js
  6. 39 0
      src/permission.js
  7. 21 0
      src/store/app.js
  8. 117 0
      src/utils/is.js

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "@mdi/font": "7.0.96",
     "axios": "^1.6.8",
     "js-cookie": "^3.0.5",
+    "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
     "pnpm": "^9.1.0",

+ 8 - 0
pnpm-lock.yaml

@@ -17,6 +17,9 @@ importers:
       js-cookie:
         specifier: ^3.0.5
         version: 3.0.5
+      nprogress:
+        specifier: ^0.2.0
+        version: 0.2.0
       pinia:
         specifier: ^2.1.7
         version: 2.1.7(vue@3.4.25)
@@ -936,6 +939,9 @@ packages:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
 
+  nprogress@0.2.0:
+    resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
+
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
@@ -2206,6 +2212,8 @@ snapshots:
 
   normalize-path@3.0.0: {}
 
+  nprogress@0.2.0: {}
+
   nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0

+ 33 - 0
src/hooks/web/useNProgress.js

@@ -0,0 +1,33 @@
+import { nextTick } from 'vue'
+// import { useCssVar } from '@vueuse/core'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+
+// const primaryColor = useCssVar('--el-color-primary', document.documentElement)
+
+export const useNProgress = () => {
+  NProgress.configure({ showSpinner: false })
+
+  const initColor = async () => {
+    await nextTick()
+    const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0]
+    if (bar) {
+      bar.style.background = 'primary'
+    }
+  }
+
+  initColor()
+
+  const start = () => {
+    NProgress.start()
+  }
+
+  const done = () => {
+    NProgress.done()
+  }
+
+  return {
+    start,
+    done
+  }
+}

+ 25 - 0
src/hooks/web/useTitle.js

@@ -0,0 +1,25 @@
+import { watch, ref } from 'vue'
+import { isString } from '@/utils/is'
+import { useAppStore } from '@/store/app'
+import { useI18n } from './useI18n'
+
+
+export const useTitle = (newTitle) => {
+  const appStore = useAppStore()
+  const { t } = useI18n()
+  const title = ref(
+    newTitle ? `${appStore.title} - ${t(newTitle)}` : appStore.title
+  )
+
+  watch(
+    title,
+    (n, o) => {
+      if (isString(n) && n !== o && document) {
+        document.title = n
+      }
+    },
+    { immediate: true }
+  )
+
+  return title
+}

+ 2 - 1
src/main.js

@@ -17,6 +17,8 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // pinia 持
 
 import router from './router'
 
+import './permission'
+
 const pinia = createPinia()
 
 pinia.use(piniaPluginPersistedstate)
@@ -24,7 +26,6 @@ pinia.use(piniaPluginPersistedstate)
 const app = createApp(App)
 
 app.use(pinia)
-
 app.use(router)
 
 registerPlugins(app)

+ 39 - 0
src/permission.js

@@ -0,0 +1,39 @@
+import router from './router'
+import { useNProgress } from '@/hooks/web/useNProgress'
+import { useTitle } from '@/hooks/web/useTitle'
+import { getToken } from '@/utils/auth'
+
+const { start, done } = useNProgress()
+// 路由不重定向白名单
+const whiteList = [
+  '/login',
+  '/social-login',
+  '/auth-redirect',
+  '/bind',
+  '/register'
+]
+
+// 路由守卫
+router.beforeEach(async (to, from, next) => {
+  start()
+  // loadStart()
+  if (getToken()) {
+    if (to.path === '/login') {
+      next({ path: '/' })
+    } else {
+      next()
+    }
+  } else {
+    if (whiteList.indexOf(to.path) !== -1) {
+      next()
+    } else {
+      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
+    }
+  }
+})
+
+router.afterEach((to) => {
+  useTitle(to?.meta?.title)
+  done() // 结束Progress
+  // loadDone()
+})

+ 21 - 0
src/store/app.js

@@ -0,0 +1,21 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue' 
+
+
+export const useAppStore = defineStore('app', 
+  () => {
+    const title = ref(import.meta.env.VITE_APP_TITLE) // 标题
+
+    const setTitle = (txt) => {
+      title.value = txt
+    }
+    return {
+      title,
+      setTitle
+    }
+  
+  },
+  {
+    persist: true, // ref() 持久化响应
+  }
+)

+ 117 - 0
src/utils/is.js

@@ -0,0 +1,117 @@
+// copy to vben-admin
+
+const toString = Object.prototype.toString
+
+export const is = (val, type) => {
+  return toString.call(val) === `[object ${type}]`
+}
+
+export const isDef = (val) => {
+  return typeof val !== 'undefined'
+}
+
+export const isUnDef = (val) => {
+  return !isDef(val)
+}
+
+export const isObject = (val) => {
+  return val !== null && is(val, 'Object')
+}
+
+export const isEmpty = (val) => {
+  if (val === null) {
+    return true
+  }
+  if (isArray(val) || isString(val)) {
+    return val.length === 0
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0
+  }
+
+  return false
+}
+
+export const isDate = (val) => {
+  return is(val, 'Date')
+}
+
+export const isNull = (val) => {
+  return val === null
+}
+
+export const isNullAndUnDef = (val) => {
+  return isUnDef(val) && isNull(val)
+}
+
+export const isNullOrUnDef = (val) => {
+  return isUnDef(val) || isNull(val)
+}
+
+export const isNumber = (val) => {
+  return is(val, 'Number')
+}
+
+export const isPromise = (val) => {
+  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
+}
+
+export const isString = (val) => {
+  return is(val, 'String')
+}
+
+export const isFunction = (val) => {
+  return typeof val === 'function'
+}
+
+export const isBoolean = (val) => {
+  return is(val, 'Boolean')
+}
+
+export const isRegExp = (val) => {
+  return is(val, 'RegExp')
+}
+
+export const isArray = (val) => {
+  return val && Array.isArray(val)
+}
+
+export const isWindow = (val) => {
+  return typeof window !== 'undefined' && is(val, 'Window')
+}
+
+export const isElement = (val) => {
+  return isObject(val) && !!val.tagName
+}
+
+export const isMap = (val) => {
+  return is(val, 'Map')
+}
+
+export const isServer = typeof window === 'undefined'
+
+export const isClient = !isServer
+
+// export const isUrl = (path) => {
+//   const reg =
+//     /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
+//   return reg.test(path)
+// }
+
+export const isDark = () => {
+  return window.matchMedia('(prefers-color-scheme: dark)').matches
+}
+
+// 是否是图片链接
+export const isImgPath = (path) => {
+  return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
+}
+
+export const isEmptyVal = (val) => {
+  return val === '' || val === null || val === undefined
+}