refactor: ♻️ 图标选择器优化

This commit is contained in:
ray
2024-10-23 00:54:10 +08:00
parent f07d68b240
commit 25cccb5d87
2 changed files with 117 additions and 101 deletions

View File

@@ -1,44 +1,47 @@
<template> <template>
<div ref="iconSelectRef" :style="'width:' + width"> <div ref="iconSelectRef" :style="{ width: props.width }">
<el-popover :visible="popoverVisible" :width="width" placement="bottom-end"> <el-popover
:visible="popoverVisible"
:width="props.width"
placement="bottom-end"
>
<template #reference> <template #reference>
<el-input <div @click="popoverVisible = !popoverVisible">
v-model="selectedIcon" <slot>
class="reference" <el-input
readonly v-model="selectedIcon"
placeholder="点击选择图标" readonly
@click="popoverVisible = !popoverVisible" placeholder="点击选择图标"
> class="reference"
<template #prepend>
<template
v-if="selectedIcon && selectedIcon.startsWith('el-icon-')"
> >
<el-icon> <template #prepend>
<component :is="selectedIcon.replace('el-icon-', '')" /> <!-- 根据图标类型展示 -->
</el-icon> <el-icon v-if="isElementIcon">
</template> <component :is="selectedIcon.replace('el-icon-', '')" />
<template v-else> </el-icon>
<svg-icon :icon-class="selectedIcon" /> <template v-else>
</template> <svg-icon :icon-class="selectedIcon" />
</template> </template>
<template #suffix> </template>
<el-icon <template #suffix>
:style="{ <el-icon
transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)', :style="{
transition: 'transform .5s', transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)',
}" transition: 'transform .5s',
@click="popoverVisible = !popoverVisible" }"
> >
<ArrowDown /> <ArrowDown @click.stop="togglePopover" />
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
</slot>
</div>
</template> </template>
<!-- 下拉选择弹窗 --> <!-- 图标选择弹窗 -->
<div ref="popoverContentRef"> <div ref="popoverContentRef">
<el-input <el-input
v-model="searchText" v-model="filterText"
placeholder="搜索图标" placeholder="搜索图标"
clearable clearable
@input="filterIcons" @input="filterIcons"
@@ -46,11 +49,11 @@
<el-tabs v-model="activeTab" @tab-click="handleTabClick"> <el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="SVG 图标" name="svg"> <el-tab-pane label="SVG 图标" name="svg">
<el-scrollbar height="300px"> <el-scrollbar height="300px">
<ul class="icon-container"> <ul class="icon-grid">
<li <li
v-for="icon in filteredSvgIcons" v-for="icon in filteredSvgIcons"
:key="'svg-' + icon" :key="'svg-' + icon"
class="icon-item" class="icon-grid-item"
@click="selectIcon(icon)" @click="selectIcon(icon)"
> >
<el-tooltip :content="icon" placement="bottom" effect="light"> <el-tooltip :content="icon" placement="bottom" effect="light">
@@ -62,11 +65,11 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Element 图标" name="element"> <el-tab-pane label="Element 图标" name="element">
<el-scrollbar height="300px"> <el-scrollbar height="300px">
<ul class="icon-container"> <ul class="icon-grid">
<li <li
v-for="icon in filteredEpIcons" v-for="icon in filteredElementIcons"
:key="icon" :key="icon"
class="icon-item" class="icon-grid-item"
@click="selectIcon(icon)" @click="selectIcon(icon)"
> >
<el-icon> <el-icon>
@@ -88,94 +91,89 @@ import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
require: false,
default: "", default: "",
}, },
width: { width: {
type: String, type: String,
require: false,
default: "500px", default: "500px",
}, },
}); });
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue"]);
const selectedIcon = toRef(props, "modelValue");
const iconSelectRef = ref(); const iconSelectRef = ref();
const popoverContentRef = ref(); const popoverContentRef = ref();
const popoverVisible = ref(false);
const activeTab = ref("svg");
const activeTab = ref("svg"); // 默认激活的Tab const svgIcons = ref<string[]>([]);
const searchText = ref(""); // 筛选的值 const elementIcons = ref<string[]>(Object.keys(ElementPlusIconsVue));
const popoverVisible = ref(false); // 弹窗显示状态 const selectedIcon = defineModel("modelValue", {
type: String,
const svgIcons: string[] = []; // SVG图标集合 required: true,
const filteredSvgIcons = ref<string[]>([]); // 过滤后的SVG图标名称集合
const epIcons: string[] = Object.keys(ElementPlusIconsVue); // Element Plus图标集合
const filteredEpIcons = ref<string[]>([]); // 过滤后的Element Plus图标名称集合
onMounted(() => {
loadIcons();
}); });
/** const filterText = ref("");
* icon 加载 const filteredSvgIcons = ref<string[]>([]);
*/ const filteredElementIcons = ref<string[]>(elementIcons.value);
const isElementIcon = computed(() => selectedIcon.value.startsWith("el-icon-"));
function loadIcons() { function loadIcons() {
const icons = import.meta.glob("../../assets/icons/*.svg"); const icons = import.meta.glob("../../assets/icons/*.svg");
for (const path in icons) { for (const path in icons) {
const iconName = path.replace(/.*\/(.*)\.svg$/, "$1"); const iconName = path.replace(/.*\/(.*)\.svg$/, "$1");
svgIcons.push(iconName); svgIcons.value.push(iconName);
} }
filteredSvgIcons.value = svgIcons; filteredSvgIcons.value = svgIcons.value;
} }
/**
* 选项卡切换
*/
function handleTabClick(tabPane: any) { function handleTabClick(tabPane: any) {
activeTab.value = tabPane.name; activeTab.value = tabPane.name;
filterIcons(); filterIcons();
} }
/**
* icon 筛选
*/
function filterIcons() { function filterIcons() {
if (activeTab.value === "svg") { if (activeTab.value === "svg") {
// 过滤SVG图标逻辑 filteredSvgIcons.value = filterText.value
filteredSvgIcons.value = searchText.value ? svgIcons.value.filter((icon) =>
? svgIcons.filter((iconName) => icon.toLowerCase().includes(filterText.value.toLowerCase())
iconName.toLowerCase().includes(searchText.value.toLowerCase())
) )
: svgIcons; : svgIcons.value;
} else { } else {
// 过滤Element Plus图标逻辑 TODO filteredElementIcons.value = filterText.value
filteredEpIcons.value = searchText.value ? elementIcons.value.filter((icon) =>
? epIcons.filter((iconName) => icon.toLowerCase().includes(filterText.value.toLowerCase())
iconName.toLowerCase().includes(searchText.value.toLowerCase())
) )
: epIcons; : elementIcons.value;
} }
} }
/** function selectIcon(icon: string) {
* 选择图标 const iconName = activeTab.value === "element" ? "el-icon-" + icon : icon;
*/
function selectIcon(iconName: string) {
if (activeTab.value === "element") {
iconName = "el-icon-" + iconName;
}
emit("update:modelValue", iconName); emit("update:modelValue", iconName);
popoverVisible.value = false; popoverVisible.value = false;
} }
/** function togglePopover() {
* 点击容器外的区域关闭弹窗 VueUse onClickOutside popoverVisible.value = !popoverVisible.value;
*/ }
onClickOutside(iconSelectRef, () => (popoverVisible.value = false), { onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
ignore: [popoverContentRef], ignore: [popoverContentRef],
}); });
onMounted(() => {
loadIcons();
if (selectedIcon.value) {
if (
elementIcons.value.includes(selectedIcon.value.replace("el-icon-", ""))
) {
activeTab.value = "element";
} else {
activeTab.value = "svg";
}
}
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -184,25 +182,25 @@ onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
cursor: pointer; cursor: pointer;
} }
.icon-container { .icon-grid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
}
.icon-item { .icon-grid-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 8px; padding: 8px;
margin: 4px; margin: 4px;
cursor: pointer; cursor: pointer;
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
border-radius: 4px; border-radius: 4px;
transition: all 0.3s; transition: all 0.3s;
} }
.icon-item:hover { .icon-grid-item:hover {
border-color: #4080ff; border-color: #4080ff;
scale: 1.2; transform: scale(1.2);
}
} }
</style> </style>

View File

@@ -0,0 +1,18 @@
<!-- 图标选择器示例 -->
<script setup lang="ts">
const iconName = ref("el-icon-edit");
</script>
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/icon-selector.vue"
type="primary"
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
</el-link>
<icon-select v-model="iconName" />
</div>
</template>