perf: 优化IconSelect图标选择器组件,提升用户体验

This commit is contained in:
xiaoxian521 2023-10-13 23:36:31 +08:00
parent fc2d905e92
commit 3e040fcc24
5 changed files with 2646 additions and 702 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,15 @@
import iconifyIconOffline from "./src/iconifyIconOffline"; import iconifyIconOffline from "./src/iconifyIconOffline";
import iconifyIconOnline from "./src/iconifyIconOnline"; import iconifyIconOnline from "./src/iconifyIconOnline";
import iconSelect from "./src/Select.vue";
import fontIcon from "./src/iconfont"; import fontIcon from "./src/iconfont";
/** 本地图标组件 */ /** 本地图标组件 */
const IconifyIconOffline = iconifyIconOffline; const IconifyIconOffline = iconifyIconOffline;
/** 在线图标组件 */ /** 在线图标组件 */
const IconifyIconOnline = iconifyIconOnline; const IconifyIconOnline = iconifyIconOnline;
/** iconfont组件 */ /** `IconSelect`图标选择器组件 */
const IconSelect = iconSelect;
/** `iconfont`组件 */
const FontIcon = fontIcon; const FontIcon = fontIcon;
export { IconifyIconOffline, IconifyIconOnline, FontIcon }; export { IconifyIconOffline, IconifyIconOnline, IconSelect, FontIcon };

View File

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { cloneDeep } from "@pureadmin/utils";
import { IconJson } from "@/components/ReIcon/data"; import { IconJson } from "@/components/ReIcon/data";
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
import { ref, computed, CSSProperties, toRef, watch } from "vue"; import { ref, computed, CSSProperties, toRef, watch } from "vue";
import Search from "@iconify-icons/ri/search-eye-line";
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined; type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
defineOptions({ defineOptions({
@ -16,15 +18,15 @@ const props = defineProps({
}); });
const emit = defineEmits<{ (e: "update:modelValue", v: string) }>(); const emit = defineEmits<{ (e: "update:modelValue", v: string) }>();
const visible = ref(false);
const inputValue = toRef(props, "modelValue"); const inputValue = toRef(props, "modelValue");
const iconList = ref(IconJson); const iconList = ref(IconJson);
const icon = ref("add-location"); const icon = ref();
const currentActiveType = ref("ep:"); const currentActiveType = ref("ep:");
// //
const copyIconList = cloneDeep(iconList.value); const copyIconList = cloneDeep(iconList.value);
const totalPage = ref(0);
const pageSize = ref(96); // 35
const pageSize = ref(35);
const currentPage = ref(1); const currentPage = ref(1);
// //
@ -36,8 +38,8 @@ const tabsList = [
name: "ep:" name: "ep:"
}, },
{ {
label: "Font Awesome 4", label: "Remix Icon",
name: "fa:" name: "ri:"
}, },
{ {
label: "Font Awesome 5 Solid", label: "Font Awesome 5 Solid",
@ -45,20 +47,14 @@ const tabsList = [
} }
]; ];
const pageList = computed(() => { const pageList = computed(() =>
if (currentPage.value === 1) { copyIconList[currentActiveType.value]
return copyIconList[currentActiveType.value] .filter(i => i.includes(filterValue.value))
.filter(v => v.includes(filterValue.value))
.slice(currentPage.value - 1, pageSize.value);
} else {
return copyIconList[currentActiveType.value]
.filter(v => v.includes(filterValue.value))
.slice( .slice(
pageSize.value * (currentPage.value - 1), (currentPage.value - 1) * pageSize.value,
pageSize.value * (currentPage.value - 1) + pageSize.value currentPage.value * pageSize.value
); )
} );
});
const iconItemStyle = computed((): ParameterCSSProperties => { const iconItemStyle = computed((): ParameterCSSProperties => {
return item => { return item => {
@ -71,50 +67,63 @@ const iconItemStyle = computed((): ParameterCSSProperties => {
}; };
}); });
function setVal() {
currentActiveType.value = props.modelValue.substring(
0,
props.modelValue.indexOf(":") + 1
);
icon.value = props.modelValue.substring(props.modelValue.indexOf(":") + 1);
}
function onBeforeEnter() {
if (isAllEmpty(icon.value)) return;
setVal();
//
const curIconIndex = copyIconList[currentActiveType.value].findIndex(
i => i === icon.value
);
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
}
function onAfterLeave() {
filterValue.value = "";
}
function handleClick({ props }) { function handleClick({ props }) {
currentPage.value = 1; currentPage.value = 1;
currentActiveType.value = props.name; currentActiveType.value = props.name;
emit(
"update:modelValue",
currentActiveType.value + iconList.value[currentActiveType.value][0]
);
icon.value = iconList.value[currentActiveType.value][0];
} }
function onChangeIcon(item) { function onChangeIcon(item) {
icon.value = item; icon.value = item;
emit("update:modelValue", currentActiveType.value + item); emit("update:modelValue", currentActiveType.value + item);
visible.value = false;
} }
function onCurrentChange(page) { function onCurrentChange(page) {
currentPage.value = page; currentPage.value = page;
} }
function onClear() {
icon.value = "";
emit("update:modelValue", "");
}
watch( watch(
() => { () => pageList.value,
return props.modelValue; () =>
}, (totalPage.value = copyIconList[currentActiveType.value].filter(i =>
() => { i.includes(filterValue.value)
if (props.modelValue) { ).length),
currentActiveType.value = props.modelValue.substring(
0,
props.modelValue.indexOf(":") + 1
);
icon.value = props.modelValue.substring(
props.modelValue.indexOf(":") + 1
);
}
},
{ immediate: true } { immediate: true }
); );
watch( watch(
() => { () => props.modelValue,
return filterValue.value; val => val && setVal(),
}, { immediate: true }
() => { );
currentPage.value = 1; watch(
} () => filterValue.value,
() => (currentPage.value = 1)
); );
</script> </script>
@ -129,14 +138,15 @@ watch(
:popper-options="{ :popper-options="{
placement: 'auto' placement: 'auto'
}" }"
:visible="visible" @before-enter="onBeforeEnter"
@after-leave="onAfterLeave"
> >
<template #reference> <template #reference>
<div <div
class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center" class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center"
@click="visible = !visible"
> >
<IconifyIconOnline :icon="currentActiveType + icon" /> <IconifyIconOffline v-if="!icon" :icon="Search" />
<IconifyIconOnline v-else :icon="inputValue" />
</div> </div>
</template> </template>
@ -160,7 +170,7 @@ watch(
v-for="(item, key) in pageList" v-for="(item, key) in pageList"
:key="key" :key="key"
:title="item" :title="item"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid" class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
:style="iconItemStyle(item)" :style="iconItemStyle(item)"
@click="onChangeIcon(item)" @click="onChangeIcon(item)"
> >
@ -175,16 +185,31 @@ watch(
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div
class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]"
>
<el-pagination <el-pagination
small class="flex-auto ml-2"
:total="copyIconList[currentActiveType].length" :total="totalPage"
:page-size="pageSize"
:current-page="currentPage" :current-page="currentPage"
:page-size="pageSize"
:pager-count="5"
layout="pager"
background background
layout="prev, pager, next" small
class="flex items-center justify-center h-10"
@current-change="onCurrentChange" @current-change="onCurrentChange"
/> />
<el-button
class="justify-end mr-2 ml-2"
type="danger"
size="small"
text
bg
@click="onClear"
>
清空
</el-button>
</div>
</el-popover> </el-popover>
</template> </template>
</el-input> </el-input>
@ -231,6 +256,14 @@ watch(
box-shadow: 0 2px 5px rgb(0 0 0 / 6%); box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
} }
:deep(.el-tabs__nav-wrap::after) {
height: 0;
}
:deep(.el-tabs__nav-wrap) {
padding: 0 24px;
}
:deep(.el-tabs__content) { :deep(.el-tabs__content) {
margin-top: 4px; margin-top: 4px;
} }

View File

@ -1,7 +1,7 @@
import { h, defineComponent } from "vue"; import { h, defineComponent } from "vue";
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline"; import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
// Iconify Icon在Vue里本地使用用于内网环境https://docs.iconify.design/icon-components/vue/offline.html // Iconify Icon在Vue里本地使用用于内网环境
export default defineComponent({ export default defineComponent({
name: "IconifyIconOffline", name: "IconifyIconOffline",
components: { IconifyIcon }, components: { IconifyIcon },

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import IconSelect from "@/components/ReIcon/src/Select.vue"; import { IconSelect } from "@/components/ReIcon";
defineOptions({ defineOptions({
name: "IconSelect" name: "IconSelect"