Jelajahi Sumber

feat(web): 菜单管理图标字段改为可视化图标选择器(282个Element图标+搜索+清除)

Xiaogang Liao 1 Minggu lalu
induk
melakukan
de7d6a284d
2 mengubah file dengan 445 tambahan dan 1 penghapusan
  1. 286 0
      cfc-web/src/utils/element-icons.js
  2. 159 1
      cfc-web/src/views/admin/MenuManage.vue

+ 286 - 0
cfc-web/src/utils/element-icons.js

@@ -0,0 +1,286 @@
+// Element UI 2.x 全量图标清单(来源于 element-ui icons.css 实测提取)
+// 供菜单管理图标选择器使用;格式保持 "el-icon-xxx" 与现有存储/渲染兼容
+export const ELEMENT_ICONS = [
+  "el-icon-add-location",
+  "el-icon-aim",
+  "el-icon-alarm-clock",
+  "el-icon-apple",
+  "el-icon-arrow-down",
+  "el-icon-arrow-left",
+  "el-icon-arrow-right",
+  "el-icon-arrow-up",
+  "el-icon-attract",
+  "el-icon-back",
+  "el-icon-bangzhu",
+  "el-icon-bank-card",
+  "el-icon-baseball",
+  "el-icon-basketball",
+  "el-icon-bell",
+  "el-icon-bicycle",
+  "el-icon-bottom",
+  "el-icon-bottom-left",
+  "el-icon-bottom-right",
+  "el-icon-box",
+  "el-icon-brush",
+  "el-icon-burger",
+  "el-icon-camera",
+  "el-icon-camera-solid",
+  "el-icon-caret-bottom",
+  "el-icon-caret-left",
+  "el-icon-caret-right",
+  "el-icon-caret-top",
+  "el-icon-chat-dot-round",
+  "el-icon-chat-dot-square",
+  "el-icon-chat-line-round",
+  "el-icon-chat-line-square",
+  "el-icon-chat-round",
+  "el-icon-chat-square",
+  "el-icon-check",
+  "el-icon-cherry",
+  "el-icon-chicken",
+  "el-icon-circle-check",
+  "el-icon-circle-close",
+  "el-icon-circle-plus",
+  "el-icon-circle-plus-outline",
+  "el-icon-close",
+  "el-icon-close-notification",
+  "el-icon-close-tip",
+  "el-icon-cloudy",
+  "el-icon-cloudy-and-sunny",
+  "el-icon-coffee",
+  "el-icon-coffee-cup",
+  "el-icon-coin",
+  "el-icon-cold-drink",
+  "el-icon-collection",
+  "el-icon-collection-tag",
+  "el-icon-connection",
+  "el-icon-coordinate",
+  "el-icon-copy-document",
+  "el-icon-cpu",
+  "el-icon-crop",
+  "el-icon-c-scale-to-original",
+  "el-icon-d-arrow-left",
+  "el-icon-d-arrow-right",
+  "el-icon-data-analysis",
+  "el-icon-data-board",
+  "el-icon-data-line",
+  "el-icon-date",
+  "el-icon-d-caret",
+  "el-icon-delete",
+  "el-icon-delete-location",
+  "el-icon-delete-solid",
+  "el-icon-dessert",
+  "el-icon-discount",
+  "el-icon-discover",
+  "el-icon-dish",
+  "el-icon-dish-1",
+  "el-icon-document",
+  "el-icon-document-add",
+  "el-icon-document-checked",
+  "el-icon-document-copy",
+  "el-icon-document-delete",
+  "el-icon-document-remove",
+  "el-icon-download",
+  "el-icon-edit",
+  "el-icon-edit-outline",
+  "el-icon-eleme",
+  "el-icon-error",
+  "el-icon-female",
+  "el-icon-files",
+  "el-icon-film",
+  "el-icon-finished",
+  "el-icon-first-aid-kit",
+  "el-icon-folder",
+  "el-icon-folder-add",
+  "el-icon-folder-checked",
+  "el-icon-folder-delete",
+  "el-icon-folder-opened",
+  "el-icon-folder-remove",
+  "el-icon-food",
+  "el-icon-football",
+  "el-icon-fork-spoon",
+  "el-icon-full-screen",
+  "el-icon-goblet",
+  "el-icon-goblet-full",
+  "el-icon-goblet-square",
+  "el-icon-goblet-square-full",
+  "el-icon-goods",
+  "el-icon-grape",
+  "el-icon-guide",
+  "el-icon-headset",
+  "el-icon-heavy-rain",
+  "el-icon-help",
+  "el-icon-hot-water",
+  "el-icon-house",
+  "el-icon-ice-cream",
+  "el-icon-ice-cream-round",
+  "el-icon-ice-cream-square",
+  "el-icon-ice-drink",
+  "el-icon-ice-tea",
+  "el-icon-info",
+  "el-icon-key",
+  "el-icon-knife-fork",
+  "el-icon-lightning",
+  "el-icon-light-rain",
+  "el-icon-link",
+  "el-icon-loading",
+  "el-icon-location",
+  "el-icon-location-information",
+  "el-icon-location-outline",
+  "el-icon-lock",
+  "el-icon-lollipop",
+  "el-icon-magic-stick",
+  "el-icon-male",
+  "el-icon-map-location",
+  "el-icon-medal",
+  "el-icon-medal-1",
+  "el-icon-menu",
+  "el-icon-message",
+  "el-icon-message-solid",
+  "el-icon-mic",
+  "el-icon-microphone",
+  "el-icon-milk-tea",
+  "el-icon-minus",
+  "el-icon-mobile",
+  "el-icon-mobile-phone",
+  "el-icon-money",
+  "el-icon-monitor",
+  "el-icon-moon",
+  "el-icon-moon-night",
+  "el-icon-more",
+  "el-icon-more-outline",
+  "el-icon-mouse",
+  "el-icon-news",
+  "el-icon-no-smoking",
+  "el-icon-notebook-1",
+  "el-icon-notebook-2",
+  "el-icon-odometer",
+  "el-icon-office-building",
+  "el-icon-open",
+  "el-icon-orange",
+  "el-icon-paperclip",
+  "el-icon-partly-cloudy",
+  "el-icon-pear",
+  "el-icon-phone",
+  "el-icon-phone-outline",
+  "el-icon-picture",
+  "el-icon-picture-outline",
+  "el-icon-picture-outline-round",
+  "el-icon-pie-chart",
+  "el-icon-place",
+  "el-icon-platform-eleme",
+  "el-icon-plus",
+  "el-icon-position",
+  "el-icon-postcard",
+  "el-icon-potato-strips",
+  "el-icon-present",
+  "el-icon-price-tag",
+  "el-icon-printer",
+  "el-icon-question",
+  "el-icon-rank",
+  "el-icon-reading",
+  "el-icon-receiving",
+  "el-icon-refresh",
+  "el-icon-refresh-left",
+  "el-icon-refresh-right",
+  "el-icon-refrigerator",
+  "el-icon-remove",
+  "el-icon-remove-outline",
+  "el-icon-right",
+  "el-icon-s-check",
+  "el-icon-school",
+  "el-icon-scissors",
+  "el-icon-s-claim",
+  "el-icon-s-comment",
+  "el-icon-s-cooperation",
+  "el-icon-s-custom",
+  "el-icon-s-data",
+  "el-icon-search",
+  "el-icon-sell",
+  "el-icon-service",
+  "el-icon-setting",
+  "el-icon-set-up",
+  "el-icon-s-finance",
+  "el-icon-s-flag",
+  "el-icon-s-fold",
+  "el-icon-s-goods",
+  "el-icon-s-grid",
+  "el-icon-share",
+  "el-icon-s-help",
+  "el-icon-ship",
+  "el-icon-s-home",
+  "el-icon-shopping-bag-1",
+  "el-icon-shopping-bag-2",
+  "el-icon-shopping-cart-1",
+  "el-icon-shopping-cart-2",
+  "el-icon-shopping-cart-full",
+  "el-icon-s-management",
+  "el-icon-s-marketing",
+  "el-icon-smoking",
+  "el-icon-soccer",
+  "el-icon-sold-out",
+  "el-icon-s-open",
+  "el-icon-s-operation",
+  "el-icon-s-opportunity",
+  "el-icon-s-order",
+  "el-icon-sort",
+  "el-icon-sort-down",
+  "el-icon-sort-up",
+  "el-icon-s-platform",
+  "el-icon-s-promotion",
+  "el-icon-s-release",
+  "el-icon-s-shop",
+  "el-icon-star-off",
+  "el-icon-star-on",
+  "el-icon-s-ticket",
+  "el-icon-s-tools",
+  "el-icon-stopwatch",
+  "el-icon-success",
+  "el-icon-sugar",
+  "el-icon-suitcase",
+  "el-icon-suitcase-1",
+  "el-icon-s-unfold",
+  "el-icon-sunny",
+  "el-icon-sunrise",
+  "el-icon-sunrise-1",
+  "el-icon-sunset",
+  "el-icon-switch-button",
+  "el-icon-table-lamp",
+  "el-icon-tableware",
+  "el-icon-takeaway-box",
+  "el-icon-thumb",
+  "el-icon-tickets",
+  "el-icon-time",
+  "el-icon-timer",
+  "el-icon-toilet-paper",
+  "el-icon-top",
+  "el-icon-top-left",
+  "el-icon-top-right",
+  "el-icon-trophy",
+  "el-icon-trophy-1",
+  "el-icon-truck",
+  "el-icon-turn-off",
+  "el-icon-turn-off-microphone",
+  "el-icon-umbrella",
+  "el-icon-unlock",
+  "el-icon-upload",
+  "el-icon-upload2",
+  "el-icon-upload-success",
+  "el-icon-user",
+  "el-icon-user-solid",
+  "el-icon-video-camera",
+  "el-icon-video-camera-solid",
+  "el-icon-video-pause",
+  "el-icon-video-play",
+  "el-icon-view",
+  "el-icon-wallet",
+  "el-icon-warning",
+  "el-icon-warning-outline",
+  "el-icon-watch",
+  "el-icon-watch-1",
+  "el-icon-water-cup",
+  "el-icon-watermelon",
+  "el-icon-wind-power",
+  "el-icon-zoom-in",
+  "el-icon-zoom-out"
+]

+ 159 - 1
cfc-web/src/views/admin/MenuManage.vue

@@ -105,7 +105,65 @@
           <el-input v-model="menuForm.path" placeholder="/xxx"></el-input>
         </el-form-item>
         <el-form-item label="图标">
-          <el-input v-model="menuForm.icon" placeholder="el-icon-s-custom"></el-input>
+          <el-popover
+            v-model="iconPickerVisible"
+            placement="bottom-start"
+            width="520"
+            trigger="click"
+            popper-class="menu-icon-picker"
+          >
+            <div class="icon-picker-panel">
+              <div class="icon-picker-search">
+                <el-input
+                  v-model="iconSearch"
+                  size="small"
+                  prefix-icon="el-icon-search"
+                  placeholder="搜索图标(如:setting / user)"
+                  clearable
+                ></el-input>
+                <el-button size="small" @click="clearIcon">清除</el-button>
+              </div>
+              <div class="icon-picker-grid">
+                <div
+                  v-for="icon in filteredIcons"
+                  :key="icon"
+                  class="icon-picker-item"
+                  :class="{ 'is-active': menuForm.icon === icon }"
+                  :title="icon"
+                  @click="selectIcon(icon)"
+                >
+                  <i :class="icon"></i>
+                  <span class="icon-picker-item-name">{{ iconName(icon) }}</span>
+                </div>
+                <el-empty
+                  v-if="!filteredIcons.length"
+                  description="未找到匹配的图标"
+                  :image-size="60"
+                ></el-empty>
+              </div>
+            </div>
+            <div slot="reference" class="icon-picker-trigger">
+              <el-input
+                :value="menuForm.icon"
+                readonly
+                placeholder="点击选择图标"
+                class="icon-picker-input"
+              >
+                <i
+                  v-if="menuForm.icon"
+                  slot="prefix"
+                  :class="menuForm.icon"
+                  class="icon-picker-input-preview"
+                ></i>
+                <i
+                  v-if="menuForm.icon"
+                  slot="suffix"
+                  class="el-input__icon el-icon-close icon-picker-clear-btn"
+                  @click.stop="clearIcon"
+                ></i>
+              </el-input>
+            </div>
+          </el-popover>
         </el-form-item>
         <el-form-item label="权限标识">
           <el-input v-model="menuForm.perm" placeholder="system:menu"></el-input>
@@ -223,6 +281,7 @@ import {
   getMenuTree, getRoleMenus, saveRoleMenus,
   createMenu, updateMenu, deleteMenu, moveMenu
 } from '@/api/menu'
+import { ELEMENT_ICONS } from '@/utils/element-icons'
 
 const ROLES = [
   { label: '超级管理员', value: 'admin' },
@@ -247,6 +306,9 @@ export default {
       submitting: false,
       menuForm: this.getEmptyForm(),
       menuRules: {},
+      // 图标选择器
+      iconPickerVisible: false,
+      iconSearch: '',
       // 授权弹窗
       authDialogVisible: false,
       authTab: 'byRole',
@@ -352,6 +414,16 @@ export default {
       const node = findNode(this.menuTree, this.menuForm.id)
       if (node && node.children) collect(node.children)
       return ids
+    },
+    // 图标选择器:按关键字过滤(支持不带 el-icon- 前缀搜索)
+    filteredIcons() {
+      const kw = (this.iconSearch || '').trim().toLowerCase()
+      if (!kw) return ELEMENT_ICONS
+      const prefix = 'el-icon-'
+      return ELEMENT_ICONS.filter(icon => {
+        const plain = icon.startsWith(prefix) ? icon.slice(prefix.length) : icon
+        return plain.toLowerCase().includes(kw) || icon.toLowerCase().includes(kw)
+      })
     }
   },
   created() {
@@ -401,6 +473,18 @@ export default {
     selectParentNode(data) {
       this.menuForm.parentId = data.id
     },
+    // === 图标选择器 ===
+    selectIcon(icon) {
+      this.menuForm.icon = icon
+      this.iconPickerVisible = false
+    },
+    clearIcon() {
+      this.menuForm.icon = ''
+      this.iconSearch = ''
+    },
+    iconName(icon) {
+      return icon.replace(/^el-icon-/, '')
+    },
     handleCreate(parentId) {
       this.isEdit = false
       this.menuForm = { ...this.getEmptyForm(), parentId }
@@ -754,4 +838,78 @@ export default {
 .custom-tree-node .el-tree-node__content {
   height: 32px;
 }
+
+/* === 图标选择器 === */
+.icon-picker-trigger {
+  width: 100%;
+}
+.icon-picker-input-preview {
+  font-size: 16px;
+  line-height: 40px;
+}
+.icon-picker-clear-btn {
+  cursor: pointer;
+  color: #909399;
+}
+.icon-picker-clear-btn:hover {
+  color: #f56c6c;
+}
+.icon-picker-panel {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.icon-picker-search {
+  display: flex;
+  gap: 8px;
+}
+.icon-picker-search .el-input {
+  flex: 1;
+}
+.icon-picker-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
+  gap: 6px;
+  max-height: 280px;
+  overflow-y: auto;
+  padding: 4px;
+}
+.icon-picker-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  padding: 8px 4px;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.15s;
+}
+.icon-picker-item i {
+  font-size: 20px;
+  color: #606266;
+}
+.icon-picker-item:hover {
+  border-color: #409eff;
+  background: #ecf5ff;
+}
+.icon-picker-item:hover i {
+  color: #409eff;
+}
+.icon-picker-item.is-active {
+  border-color: #409eff;
+  background: #ecf5ff;
+}
+.icon-picker-item.is-active i {
+  color: #409eff;
+}
+.icon-picker-item-name {
+  font-size: 10px;
+  color: #909399;
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
 </style>