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