|
@@ -105,7 +105,65 @@
|
|
|
<el-input v-model="menuForm.path" placeholder="/xxx"></el-input>
|
|
<el-input v-model="menuForm.path" placeholder="/xxx"></el-input>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="图标">
|
|
<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>
|
|
|
<el-form-item label="权限标识">
|
|
<el-form-item label="权限标识">
|
|
|
<el-input v-model="menuForm.perm" placeholder="system:menu"></el-input>
|
|
<el-input v-model="menuForm.perm" placeholder="system:menu"></el-input>
|
|
@@ -223,6 +281,7 @@ import {
|
|
|
getMenuTree, getRoleMenus, saveRoleMenus,
|
|
getMenuTree, getRoleMenus, saveRoleMenus,
|
|
|
createMenu, updateMenu, deleteMenu, moveMenu
|
|
createMenu, updateMenu, deleteMenu, moveMenu
|
|
|
} from '@/api/menu'
|
|
} from '@/api/menu'
|
|
|
|
|
+import { ELEMENT_ICONS } from '@/utils/element-icons'
|
|
|
|
|
|
|
|
const ROLES = [
|
|
const ROLES = [
|
|
|
{ label: '超级管理员', value: 'admin' },
|
|
{ label: '超级管理员', value: 'admin' },
|
|
@@ -247,6 +306,9 @@ export default {
|
|
|
submitting: false,
|
|
submitting: false,
|
|
|
menuForm: this.getEmptyForm(),
|
|
menuForm: this.getEmptyForm(),
|
|
|
menuRules: {},
|
|
menuRules: {},
|
|
|
|
|
+ // 图标选择器
|
|
|
|
|
+ iconPickerVisible: false,
|
|
|
|
|
+ iconSearch: '',
|
|
|
// 授权弹窗
|
|
// 授权弹窗
|
|
|
authDialogVisible: false,
|
|
authDialogVisible: false,
|
|
|
authTab: 'byRole',
|
|
authTab: 'byRole',
|
|
@@ -352,6 +414,16 @@ export default {
|
|
|
const node = findNode(this.menuTree, this.menuForm.id)
|
|
const node = findNode(this.menuTree, this.menuForm.id)
|
|
|
if (node && node.children) collect(node.children)
|
|
if (node && node.children) collect(node.children)
|
|
|
return ids
|
|
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() {
|
|
created() {
|
|
@@ -401,6 +473,18 @@ export default {
|
|
|
selectParentNode(data) {
|
|
selectParentNode(data) {
|
|
|
this.menuForm.parentId = data.id
|
|
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) {
|
|
handleCreate(parentId) {
|
|
|
this.isEdit = false
|
|
this.isEdit = false
|
|
|
this.menuForm = { ...this.getEmptyForm(), parentId }
|
|
this.menuForm = { ...this.getEmptyForm(), parentId }
|
|
@@ -754,4 +838,78 @@ export default {
|
|
|
.custom-tree-node .el-tree-node__content {
|
|
.custom-tree-node .el-tree-node__content {
|
|
|
height: 32px;
|
|
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>
|
|
</style>
|