Spaces:
Sleeping
Sleeping
| <template> | |
| <div ref="iconSelectRef" :style="'width:' + width" class="relative"> | |
| <el-input | |
| v-model="selectedIcon" | |
| readonly | |
| placeholder="点击选择图标" | |
| @click="popoverVisible = !popoverVisible" | |
| > | |
| <template #prepend> | |
| <template v-if="selectedIcon && selectedIcon.startsWith('el-icon-')"> | |
| <el-icon> | |
| <component :is="renderIcon(selectedIcon.replace('el-icon-', ''))" /> | |
| </el-icon> | |
| </template> | |
| <template v-else> | |
| <svg-icon :icon-class="selectedIcon" /> | |
| </template> | |
| </template> | |
| </el-input> | |
| <el-popover | |
| :popoverVisible="popoverVisible" | |
| placement="bottom-end" | |
| trigger="click" | |
| :width="width" | |
| > | |
| <template #reference> | |
| <div | |
| class="cursor-pointer text-[#999] absolute-rt height-[32px] leading-[32px] px-1" | |
| @click="popoverVisible = !popoverVisible" | |
| > | |
| <i-ep-caret-top v-show="popoverVisible" /> | |
| <i-ep-caret-bottom v-show="!popoverVisible" /> | |
| </div> | |
| </template> | |
| <!-- 下拉选择弹窗 --> | |
| <div ref="popoverContentRef"> | |
| <el-input | |
| v-model="searchText" | |
| placeholder="搜索图标" | |
| clearable | |
| @input="filterIcons" | |
| /> | |
| <el-tabs v-model="activeTab" @tab-click="handleTabClick"> | |
| <el-tab-pane label="SVG 图标" name="svg"> | |
| <el-scrollbar height="300px"> | |
| <ul class="flex flex-wrap"> | |
| <li | |
| v-for="(icon, index) in filteredSvgIcons" | |
| :key="'svg-' + index" | |
| class="icon-item" | |
| @click="selectIcon(icon)" | |
| > | |
| <el-tooltip :content="icon" placement="bottom" effect="light"> | |
| <svg-icon :icon-class="icon" /> | |
| </el-tooltip> | |
| </li> | |
| </ul> | |
| </el-scrollbar> | |
| </el-tab-pane> | |
| <el-tab-pane label="Element 图标" name="element"> | |
| <el-scrollbar height="300px"> | |
| <ul class="flex flex-wrap"> | |
| <li | |
| v-for="(icon, name) in elementIcons" | |
| :key="name" | |
| class="icon-item" | |
| @click="selectIcon(name)" | |
| > | |
| <el-icon> | |
| <component :is="icon" /> | |
| </el-icon> | |
| </li> | |
| </ul> | |
| </el-scrollbar> | |
| </el-tab-pane> | |
| </el-tabs> | |
| </div> | |
| </el-popover> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import * as ElementPlusIconsVue from "@element-plus/icons-vue"; | |
| import { ElTabPane } from "element-plus"; | |
| const props = defineProps({ | |
| modelValue: { | |
| type: String, | |
| require: false, | |
| default: "", | |
| }, | |
| width: { | |
| type: String, | |
| require: false, | |
| default: "500px", | |
| }, | |
| }); | |
| const emit = defineEmits(["update:modelValue"]); | |
| const selectedIcon = toRef(props, "modelValue"); | |
| const width = toRef(props, "width"); | |
| const iconSelectRef = ref(); | |
| const popoverContentRef = ref(); | |
| const activeTab = ref("svg"); // 默认激活的Tab | |
| const searchText = ref(""); // 筛选的值 | |
| const popoverVisible = ref(false); // 弹窗显示状态 | |
| const svgIcons: string[] = []; // SVG图标集合 | |
| const filteredSvgIcons = ref<string[]>([]); // 过滤后的SVG图标名称集合 | |
| const elementIcons = ref(ElementPlusIconsVue); // Element Plus图标集合 | |
| function handleTabClick(tabPane: any) { | |
| activeTab.value = tabPane.name; | |
| filterIcons(); | |
| } | |
| /** | |
| * icon 筛选 | |
| */ | |
| function filterIcons() { | |
| if (activeTab.value === "svg") { | |
| // 过滤SVG图标逻辑 | |
| filteredSvgIcons.value = searchText.value | |
| ? svgIcons.filter((iconName) => iconName.includes(searchText.value)) | |
| : svgIcons; | |
| } else { | |
| // 过滤Element Plus图标逻辑 TODO | |
| } | |
| } | |
| /** | |
| * 选择图标 | |
| */ | |
| function selectIcon(iconName: string) { | |
| if (activeTab.value === "element") { | |
| iconName = "el-icon-" + iconName; | |
| } | |
| emit("update:modelValue", iconName); | |
| popoverVisible.value = false; | |
| } | |
| /** | |
| * 点击容器外的区域关闭弹窗 VueUse onClickOutside | |
| */ | |
| onClickOutside(iconSelectRef, () => (popoverVisible.value = false), { | |
| ignore: [popoverContentRef], | |
| }); | |
| /** | |
| * icon 加载 | |
| */ | |
| function loadIcons() { | |
| const icons = import.meta.glob("../../assets/icons/*.svg"); | |
| for (const path in icons) { | |
| const iconName = path.replace(/.*\/(.*)\.svg$/, "$1"); | |
| svgIcons.push(iconName); | |
| } | |
| filteredSvgIcons.value = svgIcons; | |
| } | |
| /** | |
| * 渲染图标组件 | |
| */ | |
| type IconNames = keyof typeof ElementPlusIconsVue; | |
| const renderIcon = (iconName: string) => { | |
| const iconComponent = ElementPlusIconsVue[iconName as IconNames]; | |
| if (iconComponent && iconComponent.name) { | |
| return h(resolveComponent(iconComponent.name)); | |
| } | |
| return null; | |
| }; | |
| onMounted(() => { | |
| loadIcons(); | |
| }); | |
| </script> | |
| <style scoped lang="scss"> | |
| .icon-item { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 8px; | |
| margin: 4px; | |
| cursor: pointer; | |
| border: 1px solid #dcdfe6; | |
| border-radius: 4px; | |
| transition: all 0.3s; | |
| } | |
| .icon-item:hover { | |
| border-color: #409eff; | |
| scale: 1.2; | |
| } | |
| </style> | |