This commit is contained in:
Your Name
2026-03-01 14:17:59 +08:00
parent 65aa12f146
commit a07e844c47
6071 changed files with 777651 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import * as ElementPlusIcons from '@element-plus/icons-vue'
//@ts-ignore
import localIconsName from 'virtual:svg-icons-names'
export const LOCAL_ICON_PREFIX = 'local-icon-'
export const EL_ICON_PREFIX = 'el-icon-'
const elIconsName: string[] = []
for (const [, component] of Object.entries(ElementPlusIcons)) {
elIconsName.push(`${EL_ICON_PREFIX}${component.name}`)
}
export function getElementPlusIconNames() {
return elIconsName
}
export function getLocalIconNames() {
return localIconsName
}
+55
View File
@@ -0,0 +1,55 @@
<script lang="ts">
import { ElIcon } from 'element-plus'
import { createVNode, defineComponent, h, resolveComponent } from 'vue'
import { EL_ICON_PREFIX, LOCAL_ICON_PREFIX } from './index'
import svgIcon from './svg-icon.vue'
export default defineComponent({
name: 'Icon',
props: {
name: {
type: String,
required: true
},
size: {
type: [String, Number],
default: '14px'
},
color: {
type: String,
default: 'inherit'
}
},
setup(props) {
return () => {
if (props.name.indexOf(EL_ICON_PREFIX) === 0) {
// el-icon
return createVNode(
ElIcon,
{
size: props.size,
color: props.color
},
{
default: () =>
createVNode(resolveComponent(props.name.replace(EL_ICON_PREFIX, '')))
}
)
}
if (props.name.indexOf(LOCAL_ICON_PREFIX) === 0) {
// 本地icon
return h(
'i',
{
class: ['local-icon']
},
createVNode(svgIcon, { ...props })
)
}
// 如果name不符合预期的前缀,返回null
return null
}
}
})
</script>
+187
View File
@@ -0,0 +1,187 @@
<template>
<div class="icon-select">
<el-popover
trigger="contextmenu"
v-model:visible="state.popoverVisible"
:width="state.popoverWidth"
>
<div
@mouseover.stop="state.mouseoverSelect = true"
@mouseout.stop="state.mouseoverSelect = false"
>
<div>
<div class="flex justify-between">
<div class="mb-3">请选择图标</div>
<div>
<span
v-for="(item, index) in iconTabsMap"
:key="index"
class="cursor-pointer text-sm ml-2"
:class="{
'text-primary': index == tabIndex
}"
@click="tabIndex = index"
>
{{ item.name }}
</span>
</div>
</div>
<div class="h-[280px]">
<el-scrollbar>
<div class="flex flex-wrap">
<div v-for="item in iconNamesFliter" :key="item" class="m-1">
<el-button @click="handleSelect(item)">
<icon :name="item" :size="18" />
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
<template #reference>
<el-input
ref="inputRef"
v-model.trim="state.inputValue"
placeholder="搜索图标"
:autofocus="false"
:disabled="disabled"
@focus="handleFocus"
@blur="handleBlur"
clearable
>
<template #prepend>
<div class="flex items-center" v-if="modelValue">
<el-tooltip class="flex-1 w-20" :content="modelValue" placement="top">
<icon
class="mr-1"
:key="modelValue"
:name="modelValue"
:size="16"
/>
</el-tooltip>
</div>
<template v-else></template>
</template>
<template #append>
<el-button>
<icon name="el-icon-Close" :size="18" @click="handleClear" />
</el-button>
</template>
</el-input>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { useEventListener } from '@vueuse/core'
import { ElInput } from 'element-plus'
import { computed, nextTick, onMounted, reactive, shallowRef, watch } from 'vue'
import { getElementPlusIconNames, getLocalIconNames } from './index'
interface Props {
modelValue: string
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
modelValue: '',
disabled: false
})
const emits = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'change', value: string): void
}>()
const tabIndex = ref(0)
const iconTabsMap = [
{
name: 'element图标',
icons: getElementPlusIconNames()
},
{
name: '本地图标',
icons: getLocalIconNames()
}
]
const inputRef = shallowRef<InstanceType<typeof ElInput>>()
const state = reactive({
inputValue: '',
popoverVisible: false,
popoverWidth: 0,
mouseoverSelect: false,
inputFocus: false
})
// input 框聚焦
const handleFocus = () => {
state.inputFocus = state.popoverVisible = true
}
// input 框失去焦点
const handleBlur = () => {
state.inputFocus = false
state.popoverVisible = state.mouseoverSelect
}
// 选中图标
const handleSelect = (icon: string) => {
state.mouseoverSelect = state.popoverVisible = false
emits('update:modelValue', icon)
emits('change', icon)
}
//取消选中
const handleClear = () => {
emits('update:modelValue', '')
emits('change', '')
}
//根据输入框内容塞选
const iconNamesFliter = computed(() => {
const iconNames = iconTabsMap[tabIndex.value]?.icons ?? []
if (!state.inputValue) {
return iconNames
}
const inputValue = state.inputValue.toLowerCase()
return iconNames.filter((icon: string) => {
if (icon.toLowerCase().indexOf(inputValue) !== -1) {
return icon
}
})
})
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
const inputWidth = inputRef.value?.$el.offsetWidth
state.popoverWidth = inputWidth < 300 ? 300 : inputWidth
})
}
//监听body点击事件
useEventListener(document.body, 'click', () => {
state.popoverVisible = state.inputFocus || state.mouseoverSelect ? true : false
})
watch(
() => state.popoverVisible,
async (value) => {
await nextTick()
if (value) {
inputRef.value?.focus()
} else {
inputRef.value?.blur()
}
}
)
onMounted(() => {
getInputWidth()
})
</script>
+39
View File
@@ -0,0 +1,39 @@
<template>
<svg aria-hidden="true" :style="styles">
<use :xlink:href="symbolId" fill="currentColor" />
</svg>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue'
import { addUnit } from '@/utils/util'
export default defineComponent({
props: {
name: {
type: String,
required: true
},
size: {
type: [Number, String],
default: 16
},
color: {
type: String,
default: 'inherit'
}
},
setup(props) {
const symbolId = computed(() => `#${props.name}`)
const styles = computed<CSSProperties>(() => {
return {
width: addUnit(props.size),
height: addUnit(props.size),
color: props.color
}
})
return { symbolId, styles }
}
})
</script>