| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- <!-- 图标选择器 -->
- <template>
- <div ref="iconSelectRef" :style="{ width: props.width }">
- <el-popover :visible="popoverVisible" :width="props.width" placement="bottom-end">
- <template #reference>
- <div @click="popoverVisible = !popoverVisible">
- <slot>
- <el-input v-model="selectedIcon" readonly placeholder="点击选择图标" class="reference">
- <template #prepend>
- <!-- 根据图标类型展示 -->
- <el-icon v-if="isElementIcon">
- <component :is="selectedIcon.replace('el-icon-', '')" />
- </el-icon>
- <template v-else>
- <div :class="`i-svg:${selectedIcon}`" />
- </template>
- </template>
- <template #suffix>
- <!-- 清空按钮 -->
- <el-icon
- v-if="selectedIcon"
- style="margin-right: 8px"
- @click.stop="clearSelectedIcon"
- >
- <CircleClose />
- </el-icon>
- <el-icon
- :style="{
- transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)',
- transition: 'transform .5s',
- }"
- >
- <ArrowDown @click.stop="togglePopover" />
- </el-icon>
- </template>
- </el-input>
- </slot>
- </div>
- </template>
- <!-- 图标选择弹窗 -->
- <div ref="popoverContentRef">
- <el-input v-model="filterText" 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="icon-grid">
- <li
- v-for="icon in filteredSvgIcons"
- :key="'svg-' + icon"
- class="icon-grid-item"
- @click="selectIcon(icon)"
- >
- <el-tooltip :content="icon" placement="bottom" effect="light">
- <div :class="`i-svg:${icon}`" />
- </el-tooltip>
- </li>
- </ul>
- </el-scrollbar>
- </el-tab-pane>
- <el-tab-pane label="Element 图标" name="element">
- <el-scrollbar height="300px">
- <ul class="icon-grid">
- <li
- v-for="icon in filteredElementIcons"
- :key="icon"
- class="icon-grid-item"
- @click="selectIcon(icon)"
- >
- <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";
- const props = defineProps({
- modelValue: {
- type: String,
- default: "",
- },
- width: {
- type: String,
- default: "500px",
- },
- });
- const emit = defineEmits(["update:modelValue"]);
- const iconSelectRef = ref();
- const popoverContentRef = ref();
- const popoverVisible = ref(false);
- const activeTab = ref("svg");
- const svgIcons = ref<string[]>([]);
- const elementIcons = ref<string[]>(Object.keys(ElementPlusIconsVue));
- const selectedIcon = defineModel("modelValue", {
- type: String,
- required: true,
- default: "",
- });
- const filterText = ref("");
- const filteredSvgIcons = ref<string[]>([]);
- const filteredElementIcons = ref<string[]>(elementIcons.value);
- const isElementIcon = computed(() => {
- return selectedIcon.value && selectedIcon.value.startsWith("el-icon");
- });
- function loadIcons() {
- const icons = import.meta.glob("../../assets/icons/*.svg");
- for (const path in icons) {
- const iconName = path.replace(/.*\/(.*)\.svg$/, "$1");
- svgIcons.value.push(iconName);
- }
- filteredSvgIcons.value = svgIcons.value;
- }
- function handleTabClick(tabPane: any) {
- activeTab.value = tabPane.props.name;
- filterIcons();
- }
- function filterIcons() {
- if (activeTab.value === "svg") {
- filteredSvgIcons.value = filterText.value
- ? svgIcons.value.filter((icon) => icon.toLowerCase().includes(filterText.value.toLowerCase()))
- : svgIcons.value;
- } else {
- filteredElementIcons.value = filterText.value
- ? elementIcons.value.filter((icon) =>
- icon.toLowerCase().includes(filterText.value.toLowerCase())
- )
- : elementIcons.value;
- }
- }
- function selectIcon(icon: string) {
- const iconName = activeTab.value === "element" ? "el-icon-" + icon : icon;
- emit("update:modelValue", iconName);
- popoverVisible.value = false;
- }
- function togglePopover() {
- popoverVisible.value = !popoverVisible.value;
- }
- onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
- ignore: [popoverContentRef],
- });
- /**
- * 清空已选图标
- */
- function clearSelectedIcon() {
- selectedIcon.value = "";
- }
- onMounted(() => {
- loadIcons();
- if (selectedIcon.value) {
- if (elementIcons.value.includes(selectedIcon.value.replace("el-icon-", ""))) {
- activeTab.value = "element";
- } else {
- activeTab.value = "svg";
- }
- }
- });
- </script>
- <style scoped lang="scss">
- .reference :deep(.el-input__wrapper),
- .reference :deep(.el-input__inner) {
- cursor: pointer;
- }
- .icon-grid {
- display: flex;
- flex-wrap: wrap;
- }
- .icon-grid-item {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 8px;
- margin: 4px;
- cursor: pointer;
- border: 1px solid var(--el-border-color);
- border-radius: 4px;
- transition: all 0.3s;
- }
- .icon-grid-item:hover {
- border-color: var(--el-color-primary);
- transform: scale(1.2);
- }
- </style>
|