Forráskód Böngészése

feat: 添加新功能

wanggaokun 1 hónapja
szülő
commit
14aa669ee9

+ 8 - 11
plugins/app-use.ts

@@ -1,27 +1,24 @@
-// import { setupRouter } from '@/router'
-// import { setupPinia } from '@/stores'
-// import { setupAuthRoutes } from '@/router/modules/authRouts'
+import { setupRouter } from '@/router'
+import { setupPinia } from '@/stores'
+import { setupAuthRoutes } from '@/router/modules/authRouts'
 // import Icon from '@/components/Icon/index.vue'
-// import { setupDirectives } from '@/directives/index'
+import { setupDirectives } from '@/directives/index'
 import { setupElIcons } from './el-icons'
 import ElementPlus from 'element-plus'
-// import { setupI18n } from '@/lang'
 import type { App } from 'vue'
 export default {
   install(app: App<Element>) {
     app.use(ElementPlus)
     // 自定义指令
-    // setupDirectives(app)
+    setupDirectives(app)
     // // 路由(router)
-    // setupRouter(app)
+    setupRouter(app)
     // // 状态管理(store)
-    // setupPinia(app)
-
-    // setupI18n(app)
+    setupPinia(app)
     // 自定义icon
     // app.component('Icon', Icon)
     setupElIcons(app)
     // 路由
-    // setupAuthRoutes()
+    setupAuthRoutes()
   }
 }

+ 0 - 1
plugins/index.ts

@@ -4,7 +4,6 @@ import initAutoImport from './auto-import'
 import initComponents from './vue-components'
 import VueSetupExtend from 'vite-plugin-vue-setup-extend'
 import initIcons from './import-icons'
-// import initAutoTinyImport from './plugins/tiny-vue'
 import initSvgIcons from './svg-icon'
 import { ViteEjsPlugin } from 'vite-plugin-ejs'
 import vueJsx from '@vitejs/plugin-vue-jsx'

+ 11 - 27
src/App.vue

@@ -1,30 +1,14 @@
-<script setup lang="ts">
-import HelloWorld from './components/HelloWorld.vue'
-</script>
-
 <template>
-  <div>
-    <a href="https://vite.dev" target="_blank">
-      <img src="/vite.svg" class="logo" alt="Vite logo" />
-    </a>
-    <a href="https://vuejs.org/" target="_blank">
-      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
-    </a>
-  </div>
-  <HelloWorld msg="Vite + Vue" />
+  <el-config-provider>
+    <router-view />
+  </el-config-provider>
 </template>
+<script setup lang="ts">
+import { useTheme } from '@/hooks'
+import en from 'element-plus/es/locale/lang/en'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
 
-<style scoped>
-.logo {
-  height: 6em;
-  padding: 1.5em;
-  will-change: filter;
-  transition: filter 300ms;
-}
-.logo:hover {
-  filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.vue:hover {
-  filter: drop-shadow(0 0 2em #42b883aa);
-}
-</style>
+const { initTheme } = useTheme()
+initTheme()
+</script>
+<style scoped></style>

+ 12 - 0
src/directives/index.ts

@@ -0,0 +1,12 @@
+import type { App, Directive } from 'vue'
+import auth from './modules/auth'
+
+const directivesList: { [key: string]: Directive } = {
+  auth
+}
+
+export const setupDirectives = (app: App) => {
+  Object.keys(directivesList).forEach(key => {
+    app.directive(key, directivesList[key])
+  })
+}

+ 30 - 0
src/directives/modules/auth.ts

@@ -0,0 +1,30 @@
+/**
+ * v-auth
+ * 按钮权限指令
+ */
+import { useUserStore } from '@/stores'
+import type { Directive, DirectiveBinding } from 'vue'
+
+const auth: Directive = {
+  mounted(el: HTMLElement, binding: DirectiveBinding) {
+    const { value } = binding
+    const all_permission = '*:*:*'
+    const permissions = useUserStore().permissions
+
+    if (value && value instanceof Array && value.length > 0) {
+      const permissionFlag = value
+
+      const hasPermissions = permissions.some(permission => {
+        return all_permission === permission || permissionFlag.includes(permission)
+      })
+
+      if (!hasPermissions) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`请设置操作权限标签值`)
+    }
+  }
+}
+
+export default auth

+ 19 - 0
src/enums/LayoutTypeEnum.ts

@@ -0,0 +1,19 @@
+/**
+ * 菜单布局枚举
+ */
+export const enum LayoutTypeEnum {
+  /**
+   * 经典模式
+   */
+  CLASSIC = 'classic',
+
+  /**
+   * 混合模式
+   */
+  MIX = 'mix',
+
+  /**
+   * 横向模式
+   */
+  TRANSVERSE = 'transverse'
+}

+ 1 - 0
src/hooks/index.ts

@@ -0,0 +1 @@
+export * from './useTheme'

+ 3 - 0
src/hooks/interface/index.ts

@@ -0,0 +1,3 @@
+export namespace Theme {
+  export type ThemeType = 'light' | 'inverted' | 'dark'
+}

+ 74 - 0
src/hooks/useTheme.ts

@@ -0,0 +1,74 @@
+import { DEFAULT_SETTING } from '@/constants'
+import { useSettingStore } from '@/stores'
+import { Theme } from './interface'
+import { getLightColor, getDarkColor } from '@/utils/color'
+import { menuTheme } from '@/assets/styles/theme/menu'
+import { asideTheme } from '@/assets/styles/theme/aside'
+import { headerTheme } from '@/assets/styles/theme/header'
+import { ElMessage } from 'element-plus'
+export const useTheme = () => {
+  const settingStore = useSettingStore()
+  const { primary, isDark, layout, asideInverted, headerInverted } = storeToRefs(settingStore)
+  // init theme
+  const initTheme = () => {
+    switchDark()
+  }
+  const switchDark = () => {
+    const html = document.documentElement as HTMLElement
+    if (unref(isDark)) html.setAttribute('class', 'dark')
+    else html.setAttribute('class', '')
+    primaryPicker(unref(primary))
+    setAsideTheme()
+    setHeaderTheme()
+  }
+  const primaryPicker = (color: string) => {
+    if (!color) {
+      color = DEFAULT_SETTING.PRIMARY
+      ElMessage({ type: 'success', message: `主题颜色已重置为 ${DEFAULT_SETTING.PRIMARY}` })
+    }
+    document.documentElement.style.setProperty('--el-color-primary', color)
+    document.documentElement.style.setProperty(
+      '--el-color-primary-dark-2',
+      unref(isDark) ? `${getLightColor(color, 0.2)}` : `${getDarkColor(color, 0.3)}`
+    )
+    for (let i = 1; i <= 9; i++) {
+      const primaryColor = unref(isDark) ? `${getDarkColor(color, i / 10)}` : `${getLightColor(color, i / 10)}`
+      document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor)
+    }
+    settingStore.setGlobalState('primary', color)
+  }
+  // 设置菜单样式
+  const setMenuTheme = () => {
+    let type: Theme.ThemeType = 'light'
+    if (asideInverted.value) type = 'inverted'
+    if (isDark.value) type = 'dark'
+    const theme = menuTheme[type!]
+    for (const [key, value] of Object.entries(theme)) {
+      document.documentElement.style.setProperty(key, value)
+    }
+  }
+  // 设置侧边栏样式
+  const setAsideTheme = () => {
+    let type: Theme.ThemeType = 'light'
+    if (asideInverted.value) type = 'inverted'
+    if (isDark.value) type = 'dark'
+    const theme = asideTheme[type!]
+    for (const [key, value] of Object.entries(theme)) {
+      document.documentElement.style.setProperty(key, value)
+    }
+    setMenuTheme()
+  }
+
+  // 设置头部样式
+  const setHeaderTheme = () => {
+    let type: Theme.ThemeType = 'light'
+    if (headerInverted.value) type = 'inverted'
+    if (isDark.value) type = 'dark'
+    const theme = headerTheme[type!]
+    for (const [key, value] of Object.entries(theme)) {
+      document.documentElement.style.setProperty(key, value)
+    }
+    setMenuTheme()
+  }
+  return { primaryPicker, initTheme, switchDark }
+}

+ 3 - 0
src/main.ts

@@ -4,6 +4,9 @@ import App from './App.vue'
 import setupAppUse from '../plugins/app-use'
 
 import '@/assets/styles/index.scss'
+
+// svg图标注册
+import 'virtual:svg-icons-register'
 // 创建实例
 const setupAll = async () => {
   const app = createApp(App)

+ 1 - 0
src/stores/index.ts

@@ -8,4 +8,5 @@ export const setupPinia = (app: App<Element>) => {
 pinia.use(piniaPluginPersistedstate)
 export * from './modules/auth'
 export * from './modules/user'
+export * from './modules/setting'
 export { pinia }

+ 26 - 0
src/stores/modules/setting.ts

@@ -0,0 +1,26 @@
+import { SettingState } from '@/stores/interface'
+import { LayoutTypeEnum } from '@/enums/LayoutTypeEnum'
+import { DEFAULT_SETTING } from '@/constants'
+export const useSettingStore = defineStore('eco-setting', {
+  state: (): SettingState => ({
+    layout: LayoutTypeEnum.CLASSIC, // mix | classic | transverse
+    breadcrumb: true,
+    breadcrumbIcon: true,
+    footer: true,
+    isDark: false,
+    primary: DEFAULT_SETTING.PRIMARY,
+    // 侧边栏反转
+    asideInverted: false,
+    // 头部反转
+    headerInverted: false,
+    showTaps: DEFAULT_SETTING.SHOW_TAPS,
+    tagsViewIcon: DEFAULT_SETTING.TAPS_VIEW_ICON
+  }),
+  getters: {},
+  actions: {
+    setGlobalState(...args: ObjToKeyValArray<SettingState>) {
+      this.$patch({ [args[0]]: args[1] })
+    }
+  },
+  persist: true
+})

+ 1 - 0
src/types/auto-components.d.ts

@@ -11,6 +11,7 @@ declare module 'vue' {
     AppClassic: typeof import('./../layouts/container/AppClassic/index.vue')['default']
     AppTransverse: typeof import('./../layouts/container/AppTransverse/index.vue')['default']
     HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
+    LoginForm: typeof import('./../views/login/components/LoginForm.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }

+ 58 - 0
src/utils/color.ts

@@ -0,0 +1,58 @@
+import { ElMessage } from 'element-plus'
+
+/**
+ * @description hex颜色转rgb颜色
+ * @param {String} str 颜色值字符串
+ * @returns {String} 返回处理后的颜色值
+ */
+export const hexToRgb = (hex: string) => {
+  let hexs: any = ''
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(hex)) return ElMessage.warning('输入错误的hex')
+  hex = hex.replace('#', '')
+  hexs = hex.match(/../g)
+  for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16)
+  return hexs
+}
+
+/**
+ * @description rgb颜色转Hex颜色
+ * @param {*} r 代表红色
+ * @param {*} g 代表绿色
+ * @param {*} b 代表蓝色
+ * @returns {String} 返回处理后的颜色值 类似#ff00ff
+ */
+export const rgbToHex = (r: any, g: any, b: any) => {
+  const reg = /^\d{1,3}$/
+  if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning('输入错误的rgb颜色值')
+  const hex = ((r << 16) | (g << 8) | b).toString(16)
+  return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex
+}
+
+/**
+ * @description 加深颜色值
+ * @param {String} color 颜色值字符串
+ * @param {Number} level 加深的程度,限0-1之间
+ * @returns {String} 返回处理后的颜色值
+ */
+export const getDarkColor = (color: string, level: number) => {
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+  const rgb = hexToRgb(color)
+  for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level))
+  return rgbToHex(rgb[0], rgb[1], rgb[2])
+}
+
+/**
+ * @description 变浅颜色值
+ * @param {String} color 颜色值字符串
+ * @param {Number} level 加深的程度,限0-1之间
+ * @returns {String} 返回处理后的颜色值
+ */
+export const getLightColor = (color: string, level: number) => {
+  const reg = /^\#?[0-9A-Fa-f]{6}$/
+  if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+  const rgb = hexToRgb(color)
+  for (let i = 0; i < 3; i++) rgb[i] = Math.round(255 * level + rgb[i] * (1 - level))
+  return rgbToHex(rgb[0], rgb[1], rgb[2])
+}

+ 7 - 0
src/views/login/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div>登录页面</div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped lang="scss"></style>

+ 0 - 18
tsconfig.app.json

@@ -1,18 +0,0 @@
-{
-  "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "compilerOptions": {
-    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
-
-    /* Linting */
-    "strict": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "noFallthroughCasesInSwitch": true,
-    "noUncheckedSideEffectImports": true,
-    "baseUrl": "./",
-    "paths": {
-      "@/*": ["src/*"]
-    }
-  },
-  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
-}

+ 31 - 5
tsconfig.json

@@ -1,7 +1,33 @@
 {
-  "files": [],
-  "references": [
-    { "path": "./tsconfig.app.json" },
-    { "path": "./tsconfig.node.json" }
-  ]
+  "compilerOptions": {
+    "target": "esnext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "strictFunctionTypes": false,
+    "strict": true,
+    "noImplicitAny": false,
+    "noLib": false,
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "lib": ["esnext", "dom"],
+    "baseUrl": "./",
+    "allowJs": true,
+    "skipLibCheck": true,
+    "allowSyntheticDefaultImports": true,
+    "forceConsistentCasingInFileNames": true,
+    "jsx": "preserve",
+    "jsxFactory": "h",
+    "jsxFragmentFactory": "Fragment",
+    "paths": {
+      "@/*": ["src/*"],
+      "#/*": ["types/*"]
+    },
+    "typeRoots": ["./node_modules/@types/", "./types"],
+    "types": ["vite/client"]
+  },
+  "include": ["mock/**/*.ts", "src/types/*.d.ts", "types/*.d.ts", "src/**/*.ts", "src/**/*.vue", "vite.config.ts"],
+  // 需要忽略的文件
+  "exclude": ["node_modules", "dist", "**/*.js"]
 }

+ 0 - 24
tsconfig.node.json

@@ -1,24 +0,0 @@
-{
-  "compilerOptions": {
-    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
-    "target": "ES2023",
-    "lib": ["ES2023"],
-    "module": "ESNext",
-    "skipLibCheck": true,
-
-    /* Bundler mode */
-    "moduleResolution": "bundler",
-    "allowImportingTsExtensions": true,
-    "verbatimModuleSyntax": true,
-    "moduleDetection": "force",
-    "noEmit": true,
-
-    /* Linting */
-    "strict": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "noFallthroughCasesInSwitch": true,
-    "noUncheckedSideEffectImports": true
-  },
-  "include": ["vite.config.ts", "src/types/*.d.ts"]
-}