Browse Source

feat: icon 选择

Gaokun Wang 4 weeks ago
parent
commit
b45faff021

+ 1 - 0
src/assets/icons/org.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1711430282380" class="icon" viewBox="0 0 1042 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1749" xmlns:xlink="http://www.w3.org/1999/xlink" width="203.515625" height="200"><path d="M991.085714 658.285714h-36.571428v-129.828571c0-20.114286-16.457143-36.571429-36.571429-36.571429h-365.714286V365.714286h36.571429c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429h-146.285714c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h36.571428v126.171428h-365.714285c-20.114286 0-36.571429 16.457143-36.571429 36.571429V658.285714h-36.571429c-20.114286 0-36.571429 16.457143-36.571428 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571428 36.571429h146.285715c20.114286 0 36.571429-16.457143 36.571428-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571428-36.571429h-36.571429v-93.257143h329.142857V658.285714h-36.571428c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h146.285714c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429h-36.571429v-93.257143h329.142858V658.285714h-36.571429c-20.114286 0-36.571429 16.457143-36.571429 36.571429v146.285714c0 20.114286 16.457143 36.571429 36.571429 36.571429h146.285714c20.114286 0 36.571429-16.457143 36.571429-36.571429v-146.285714c0-20.114286-16.457143-36.571429-36.571429-36.571429z"  p-id="1750"></path></svg>

+ 13 - 0
src/assets/icons/scope.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg t="1720242971107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4270"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+    <path d="M858.925 913.968H613.599a77.328 77.328 0 0 1-77.952-77.328V663.476a77.952 77.952 0 0 1 77.952-75.624h245.326a78.52 78.52 0 0 1 77.952 77.952v173.164a77.952 77.952 0 0 1-77.952 75zM613.599 647.863a18.452 18.452 0 0 0-18.452 17.884v173.164a18.452 18.452 0 0 0 18.452 17.885h245.326a17.884 17.884 0 0 0 17.884-17.885V665.747a17.884 17.884 0 0 0-17.884-17.884z"
+          p-id="4271"></path>
+    <path d="M837.577 645.024a29.41 29.41 0 0 1-29.41-30.034v-69.833a73.808 73.808 0 1 0-147.217 0v69.833a30.034 30.034 0 1 1-57.74 0v-69.833a133.365 133.365 0 1 1 266.105 0v69.833a30.034 30.034 0 0 1-31.738 30.034zM694.447 733.31a41.56 41.56 0 1 0 41.56-41.56 41.56 41.56 0 0 0-41.56 41.56z"
+          p-id="4272"></path>
+    <path d="M737.142 838.4a30.034 30.034 0 0 1-30.034-30.034v-51.949a30.034 30.034 0 0 1 57.74 0v51.95a29.466 29.466 0 0 1-27.706 30.033zM491.816 292.905c-40.991 0-404.068 0-404.068-115.424S450.2 62 491.816 62s404.07 0 404.07 115.424-362.51 115.48-404.07 115.48zM150.654 177.48c24.811 21.347 147.616 54.277 341.162 54.277s316.352-32.93 341.162-54.277c-24.81-21.348-147.615-54.845-341.162-54.845s-316.35 35.2-341.162 56.775z m261.507 338.266c-98.732-5.677-323.619-26.57-323.619-115.424a30.034 30.034 0 1 1 57.74 0c7.495 9.822 86.583 43.887 264.97 52.517a28.842 28.842 0 0 1 28.388 31.17 29.466 29.466 0 0 1-27.138 31.737zM369.466 735.07c-129.334-9.822-281.15-36.961-281.15-109.122a30.034 30.034 0 0 1 57.74 0c6.358 8.062 71.593 38.096 223.41 49.621a30.034 30.034 0 0 1 0 57.74z"
+          p-id="4273"></path>
+    <path d="M430.045 961.886c-102.195-3.463-340.651-22.71-340.651-115.423V179.184a30.034 30.034 0 0 1 57.74 0v669.947c9.255 12.718 102.763 46.158 282.854 53.085a30.034 30.034 0 0 1 28.842 31.17 29.466 29.466 0 0 1-28.785 28.5z m435.238-590.461a29.466 29.466 0 0 1-29.466-28.842V179.184a30.034 30.034 0 1 1 57.74 0v163.342a30.034 30.034 0 0 1-28.274 28.842z"
+          p-id="4274"></path>
+</svg>

File diff suppressed because it is too large
+ 4 - 0
src/assets/icons/zip.svg


+ 189 - 0
src/components/IconChoose/index.vue

@@ -0,0 +1,189 @@
+<template>
+  <div class="choose-container">
+    <el-input ref="refInput" v-model="internalValue" v-bind="$attrs" readonly @input="changeValue" @click="showPopover">
+      <template #prepend>
+        <el-icon size="18">
+          <SvgIcon v-if="internalValue.startsWith('svg-')" :name="internalValue.substring(4)" />
+          <component v-else-if="internalValue" :is="internalValue" />
+        </el-icon>
+      </template>
+    </el-input>
+
+    <div ref="chooseDialogRef" class="choose-wrap" :class="{ show: isShow }">
+      <el-input v-model="search" class="w-50 m-2" placeholder="搜索图标" prefix-icon="Search" />
+
+      <el-tabs v-model="activeTab">
+        <el-tab-pane label="Element" name="element">
+          <el-scrollbar max-height="280">
+            <div class="choose-box">
+              <div class="choose-item" v-for="item in filteredElementIcons" :key="item">
+                <div @click="chooseIcon(item)">
+                  <el-icon size="30">
+                    <component :is="item" />
+                  </el-icon>
+                  {{ item }}
+                </div>
+              </div>
+            </div>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane label="Svg" name="svg">
+          <el-scrollbar max-height="280">
+            <div class="choose-box">
+              <div class="choose-item" v-for="item in filteredSvgIcons" :key="item">
+                <div @click="chooseIcon(item)">
+                  <el-icon size="30">
+                    <SvgIcon :name="item.substring(4)" />
+                  </el-icon>
+                  {{ item }}
+                </div>
+              </div>
+            </div>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="IconChoose">
+// element icons
+import * as Icons from '@element-plus/icons-vue'
+
+defineOptions({
+  name: 'IconChoose'
+})
+type Props = {
+  modelValue: string
+}
+const props = defineProps<Props>()
+const emits = defineEmits<{
+  'update:modelValue': [string]
+}>()
+
+const internalValue = ref(props.modelValue)
+
+const changeValue = () => {
+  emits('update:modelValue', internalValue.value)
+}
+
+const activeTab = ref('element')
+
+// 图标搜索
+const search = ref('')
+const elementIcons = Object.keys(Icons).map(key => {
+  return Icons[key as keyof typeof Icons].name
+})
+
+const svgIcons: string[] = []
+const svgIconsFiles = import.meta.glob('@/assets/icons/**/*.svg')
+for (const key in svgIconsFiles) {
+  const matchArray = key.match(/\/assets\/icons\/([^/]+)\.svg$/)
+  if (matchArray && matchArray.length >= 2) {
+    svgIcons.push(`svg-${matchArray[1]}`)
+  }
+}
+
+// 过滤图标
+const filteredElementIcons = computed(() => {
+  return elementIcons.filter(item => item?.toLowerCase().includes(search.value.toLowerCase()))
+})
+
+const filteredSvgIcons = computed(() => {
+  return svgIcons.filter(item => item.toLowerCase().includes(search.value.toLowerCase()))
+})
+
+/**
+ * 弹窗
+ */
+const isShow = ref(false)
+
+/**
+ * 显示
+ */
+const showPopover = () => {
+  isShow.value = true
+}
+
+/**
+ * 隐藏
+ */
+const hidePopover = () => {
+  isShow.value = false
+}
+
+// 弹窗外部触发
+const chooseDialogRef = ref(null)
+onClickOutside(chooseDialogRef, () => {
+  if (isShow.value) {
+    hidePopover()
+  }
+})
+
+const chooseIcon = (name: string | undefined) => {
+  if (!name) {
+    return
+  }
+  hidePopover()
+  internalValue.value = name
+  emits('update:modelValue', name)
+}
+</script>
+
+<style scoped lang="scss">
+.choose-container {
+  position: relative;
+  width: 100%;
+  .choose-wrap {
+    position: absolute;
+    margin-top: 6px;
+    padding: 10px;
+    z-index: 100;
+    border: 1px solid #e4e7ed;
+    border-radius: 6px;
+    box-shadow: 0 0 12px rgb(0 0 0 / 5%);
+    background-color: #ffffff;
+    width: 100%;
+    height: 0;
+    opacity: 0;
+    overflow: hidden;
+    transition:
+      height 0.3s,
+      opacity 0.3s;
+    box-sizing: border-box;
+    &.show {
+      opacity: 1;
+      height: 400px;
+    }
+  }
+}
+.choose-box {
+  display: flex;
+  flex-wrap: wrap;
+  margin: 0 10px;
+  .choose-item {
+    width: 25%;
+    padding: 5px;
+    box-sizing: border-box;
+    > div {
+      display: flex;
+      align-items: center;
+      padding: 5px;
+      border: 1px solid rgb(204 204 204 / 50%);
+      border-radius: 4px;
+      overflow: hidden;
+      font-size: 12px;
+    }
+    &:hover {
+      > div {
+        border-color: rgb(0 150 136 / 50%);
+      }
+    }
+  }
+}
+.close-box {
+  position: absolute;
+  top: -15px;
+  right: -15px;
+}
+</style>

+ 4 - 0
src/components/TreeFilter/index.scss

@@ -25,6 +25,7 @@
     :deep(.el-tree--highlight-current) {
       .el-tree-node.is-current > .el-tree-node__content {
         background-color: var(--el-color-primary);
+        border-radius: 8px;
         .el-tree-node__label,
         .el-tree-node__expand-icon {
           color: white;
@@ -33,6 +34,9 @@
           color: transparent;
         }
       }
+      .el-tree-node__content:hover {
+        border-radius: 8px;
+      }
     }
   }
 }

+ 3 - 0
src/layouts/components/AppMain/index.vue

@@ -16,3 +16,6 @@ const isRouterShow = ref(true)
 const refreshCurrentPage = (val: boolean) => (isRouterShow.value = val)
 provide('refresh', refreshCurrentPage)
 </script>
+<style lang="scss" scoped>
+@use '../../container/index';
+</style>

+ 41 - 0
src/layouts/components/AppMenu/MenuItem.vue

@@ -0,0 +1,41 @@
+<template>
+  <template v-for="subItem in menuList" :key="subItem.path">
+    <el-sub-menu v-if="subItem.children?.length" :index="subItem.path">
+      <template #title>
+        <el-icon>
+          <SvgIcon v-if="subItem.meta?.icon.startsWith('svg-')" :name="subItem.meta?.icon.substring(4)" />
+          <component v-else :is="subItem.meta?.icon" />
+        </el-icon>
+        <span class="sle">{{ subItem.meta.title }}</span>
+      </template>
+      <MenuItem :menu-list="subItem.children" />
+    </el-sub-menu>
+    <el-menu-item v-else :index="subItem.path" @click="handleClickMenu(subItem)">
+      <el-icon>
+        <SvgIcon v-if="subItem.meta?.icon.startsWith('svg-')" :name="subItem.meta?.icon.substring(4)" />
+        <component v-else :is="subItem.meta?.icon" />
+      </el-icon>
+      <template #title>
+        <span class="sle">{{ subItem.meta.title }}</span>
+      </template>
+    </el-menu-item>
+  </template>
+</template>
+
+<script setup lang="ts" name="MenuItem">
+import SvgIcon from '@/components/SvgIcon/index.vue'
+
+type Props = {
+  menuList: Menu.MenuOptions[]
+}
+
+defineProps<Props>()
+
+const router = useRouter()
+const handleClickMenu = (subItem: Menu.MenuOptions) => {
+  if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
+  router.push(subItem.path)
+}
+</script>
+
+<style lang="scss"></style>

+ 37 - 28
src/layouts/components/AppMenu/index.vue

@@ -1,32 +1,41 @@
 <template>
-  <el-menu ellipsis mode="horizontal" :popper-offset="0" style="max-width: 900px">
-    <el-menu-item index="1">首页</el-menu-item>
-    <el-sub-menu index="2">
-      <template #title>权限中心</template>
-      <el-menu-item index="2-1">用户管理</el-menu-item>
-      <el-menu-item index="2-2">角色管理</el-menu-item>
-      <el-menu-item index="2-3">菜单管理</el-menu-item>
-      <el-sub-menu index="2-4">
-        <template #title>item four</template>
-        <el-menu-item index="2-4-1">item one</el-menu-item>
-        <el-menu-item index="2-4-2">item two</el-menu-item>
-        <el-menu-item index="2-4-3">item three</el-menu-item>
+  <el-menu ellipsis mode="horizontal" :router="false" :default-active="activeMenu" style="max-width: 900px">
+    <!-- 不能直接使用 MenuItem 组件,无法触发 el-menu 隐藏省略功能 -->
+    <template v-for="subItem in menuList" :key="subItem.path">
+      <el-sub-menu v-if="subItem.children?.length" :key="subItem.path" :index="subItem.path + 'el-sub-menu'">
+        <template #title>
+          <el-icon>
+            <SvgIcon v-if="subItem.meta?.icon.startsWith('svg-')" :name="subItem.meta?.icon.substring(4)" />
+            <component v-else :is="subItem.meta?.icon" />
+          </el-icon>
+          <span>{{ subItem.meta.title }}</span>
+        </template>
+        <MenuItem :menu-list="subItem.children" />
       </el-sub-menu>
-    </el-sub-menu>
-    <el-sub-menu index="3">
-      <template #title>组织架构</template>
-      <el-menu-item index="3-1">职位管理</el-menu-item>
-      <el-menu-item index="3-2">组织管理</el-menu-item>
-      <el-menu-item index="3-3">item three</el-menu-item>
-      <el-sub-menu index="3-4">
-        <template #title>demo</template>
-        <el-menu-item index="3-4-1">item one</el-menu-item>
-        <el-menu-item index="3-4-2">item two</el-menu-item>
-        <el-menu-item index="3-4-3">item three</el-menu-item>
-      </el-sub-menu>
-    </el-sub-menu>
-    <el-menu-item index="4">监控中心</el-menu-item>
-    <el-menu-item index="5">系统配置</el-menu-item>
+      <el-menu-item v-else :key="subItem.path + 'el-menu-item'" :index="subItem.path" @click="handleClickMenu(subItem)">
+        <el-icon>
+          <SvgIcon v-if="subItem.meta?.icon.startsWith('svg-')" :name="subItem.meta?.icon.substring(4)" />
+          <component v-else :is="subItem.meta?.icon" />
+        </el-icon>
+        <template #title>
+          <span>{{ subItem.meta.title }}</span>
+        </template>
+      </el-menu-item>
+    </template>
   </el-menu>
 </template>
-<script lang="ts" name="AppMenu" setup></script>
+<script lang="ts" name="AppMenu" setup>
+import { useAuthStore } from '@/stores'
+
+const route = useRoute()
+const router = useRouter()
+const authStore = useAuthStore()
+const menuList = computed(() => authStore.showMenuListGet)
+console.log('menuList', menuList)
+
+const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
+const handleClickMenu = (subItem: Menu.MenuOptions) => {
+  if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
+  router.push(subItem.path)
+}
+</script>

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

@@ -50,7 +50,9 @@ declare module 'vue' {
     Grid: typeof import('./../components/Grid/index.vue')['default']
     GridItem: typeof import('./../components/Grid/GridItem.vue')['default']
     HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
+    IconChoose: typeof import('./../components/IconChoose/index.vue')['default']
     LoginForm: typeof import('./../views/login/components/LoginForm.vue')['default']
+    MenuItem: typeof import('./../layouts/components/AppMenu/MenuItem.vue')['default']
     MoreButton: typeof import('./../layouts/components/AppTabs/MoreButton.vue')['default']
     Pagination: typeof import('./../components/ProTable/Pagination.vue')['default']
     ProTable: typeof import('./../components/ProTable/index.vue')['default']

+ 1 - 1
src/utils/index.ts

@@ -154,7 +154,7 @@ export function getShowMenuList(menuList: Menu.MenuOptions[]): Menu.MenuOptions[
   const newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList))
   return newMenuList.filter(item => {
     if (item.children?.length) item.children = getShowMenuList(item.children)
-    return item.meta?.isHidden === 'F'
+    return item.meta?.isHidden
   })
 }
 

+ 8 - 0
src/views/demo/Icon.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <IconChoose v-model="icon" placeholder="请填写图标" clearable />
+  </div>
+</template>
+<script lang="tsx" setup name="Home">
+const icon = ref('')
+</script>

+ 7 - 1
vite.config.ts

@@ -70,7 +70,13 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => {
         'element-plus/es/components/space/style/css',
         'element-plus/es/components/tooltip/style/css',
         'element-plus/es/components/tree/style/css',
-        'element-plus/es/components/scrollbar/style/css'
+        'element-plus/es/components/scrollbar/style/css',
+        'element-plus/es/components/tabs/style/css',
+        'element-plus/es/components/tab-pane/style/css',
+        'sortablejs',
+        'element-plus/es/components/dropdown/style/css',
+        'element-plus/es/components/dropdown-menu/style/css',
+        'element-plus/es/components/dropdown-item/style/css'
       ]
     }
   }

Some files were not shown because too many files changed in this diff