重构IconPicker组件
This commit is contained in:
parent
d7be207545
commit
5217ca41d2
@ -17,100 +17,109 @@
|
||||
</template>
|
||||
</n-button>
|
||||
|
||||
<!-- 图标选择弹窗 -->
|
||||
<n-modal
|
||||
<!-- 图标选择抽屉 -->
|
||||
<n-drawer
|
||||
v-model:show="showPicker"
|
||||
preset="dialog"
|
||||
:title="t('Select Icon')"
|
||||
:positive-text="t('Confirm')"
|
||||
:negative-text="t('Cancel')"
|
||||
@positive-click="confirmSelection"
|
||||
@negative-click="cancelSelection"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
style="width: 90vw; max-width: 1200px;"
|
||||
:width="900"
|
||||
:placement="'right'"
|
||||
:trap-focus="false"
|
||||
:close-on-esc="true"
|
||||
>
|
||||
<div class="icon-picker-content">
|
||||
<!-- 搜索栏和图标库选择 -->
|
||||
<div class="search-section">
|
||||
<div class="search-controls">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
:placeholder="t('Search icon name...')"
|
||||
clearable
|
||||
size="large"
|
||||
@update:value="handleSearch"
|
||||
style="flex: 1;"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:search`" />
|
||||
</template>
|
||||
</n-input>
|
||||
<n-select
|
||||
v-model:value="currentLibrary"
|
||||
:options="libraryOptions"
|
||||
:placeholder="t('Icon Library')"
|
||||
size="large"
|
||||
style="width: 200px; margin-left: 12px;"
|
||||
@update:value="handleLibraryChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="icon-count">
|
||||
{{ t('Total') }} {{ filteredIcons.length }} {{ t('icons') }} ({{ currentLibraryConfig.displayName }})
|
||||
<span v-if="loading" class="loading-text">{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标网格 -->
|
||||
<div class="icon-grid" @scroll="handleScroll">
|
||||
<div
|
||||
v-for="icon in displayedIcons"
|
||||
:key="icon"
|
||||
class="icon-item"
|
||||
:class="{ active: tempSelectedIcon === icon }"
|
||||
@click="selectIcon(icon)"
|
||||
:title="icon"
|
||||
>
|
||||
<!-- 复制按钮 -->
|
||||
<button
|
||||
class="copy-button"
|
||||
@click.stop="copyIconName(icon)"
|
||||
:title="t('Copy icon name')"
|
||||
>
|
||||
{{ t('Copy') }}
|
||||
</button>
|
||||
|
||||
<div class="icon-wrapper">
|
||||
<Icon
|
||||
:icon="currentLibraryConfig.name === 'all' ? icon : `${currentLibraryConfig.prefix}:${icon}`"
|
||||
:width="32"
|
||||
:height="32"
|
||||
@error="handleIconError"
|
||||
/>
|
||||
<n-drawer-content :title="t('Select Icon')" :closable="true">
|
||||
<template #default>
|
||||
<div class="icon-picker-content">
|
||||
<!-- 搜索栏和图标库选择 -->
|
||||
<div class="search-section">
|
||||
<div class="search-controls">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
:placeholder="t('Search icon name...')"
|
||||
clearable
|
||||
size="large"
|
||||
@update:value="handleSearch"
|
||||
style="flex: 1;"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:search`" />
|
||||
</template>
|
||||
</n-input>
|
||||
<n-select
|
||||
v-model:value="currentLibrary"
|
||||
:options="libraryOptions"
|
||||
:placeholder="t('Icon Library')"
|
||||
size="large"
|
||||
style="width: 200px; margin-left: 12px;"
|
||||
@update:value="handleLibraryChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="icon-count">
|
||||
{{ t('Total') }} {{ filteredIcons.length }} {{ t('icons') }} ({{ currentLibraryConfig.displayName }})
|
||||
<span v-if="loading" class="loading-text">{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标网格 -->
|
||||
<div class="icon-grid" @scroll="handleScroll">
|
||||
<div
|
||||
v-for="icon in displayedIcons"
|
||||
:key="icon"
|
||||
class="icon-item"
|
||||
:class="{ active: tempSelectedIcon === icon }"
|
||||
@click="selectIcon(icon)"
|
||||
:title="icon"
|
||||
>
|
||||
<!-- 复制按钮 -->
|
||||
<button
|
||||
class="copy-button"
|
||||
@click.stop="copyIconName(icon)"
|
||||
:title="t('Copy icon name')"
|
||||
>
|
||||
{{ t('Copy') }}
|
||||
</button>
|
||||
|
||||
<div class="icon-wrapper">
|
||||
<Icon
|
||||
:icon="currentLibraryConfig.name === 'all' ? icon : `${currentLibraryConfig.prefix}:${icon}`"
|
||||
:width="32"
|
||||
:height="32"
|
||||
@error="handleIconError"
|
||||
/>
|
||||
</div>
|
||||
<div class="icon-name">{{ currentLibraryConfig.name === 'all' ? icon.split(':')[1] || icon : icon }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多按钮 -->
|
||||
<div v-if="hasMoreIcons && !loading" class="load-more" @click="loadMoreIcons">
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:plus`" :width="24" :height="24" />
|
||||
<span>{{ t('Load More') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载中状态 -->
|
||||
<div v-if="loading" class="loading-more">
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:loader`" :width="24" :height="24" class="spinning" />
|
||||
<span>{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-name">{{ currentLibraryConfig.name === 'all' ? icon.split(':')[1] || icon : icon }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多按钮 -->
|
||||
<div v-if="hasMoreIcons && !loading" class="load-more" @click="loadMoreIcons">
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:plus`" :width="24" :height="24" />
|
||||
<span>{{ t('Load More') }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<n-button @click="cancelSelection">{{ t('Cancel') }}</n-button>
|
||||
<n-button type="primary" @click="confirmSelection" :disabled="!tempSelectedIcon">
|
||||
{{ t('Confirm') }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 加载中状态 -->
|
||||
<div v-if="loading" class="loading-more">
|
||||
<Icon :icon="`${currentLibraryConfig.prefix}:loader`" :width="24" :height="24" class="spinning" />
|
||||
<span>{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { NButton, NModal, NInput, NSelect, useMessage } from 'naive-ui'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { NButton, NDrawer, NDrawerContent, NInput, NSelect, useMessage } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { getIconLibraryConfig, getAvailableIconLibraries, DEFAULT_ICON_LIBRARY } from '@/shared/utils/icon-libraries'
|
||||
import { t } from '@/shared/i18n'
|
||||
@ -414,10 +423,27 @@ async function copyIconName(iconName: string) {
|
||||
message.success(`${t('Icon name copied to clipboard')}: ${fullIconName}`)
|
||||
}
|
||||
|
||||
// 监听 modelValue 变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (newValue !== selectedIcon.value) {
|
||||
selectedIcon.value = newValue || ''
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 组件挂载时加载图标
|
||||
onMounted(() => {
|
||||
loadAllIcons()
|
||||
})
|
||||
|
||||
// 暴露给外部的方法:打开选择器
|
||||
function open() {
|
||||
openPicker()
|
||||
}
|
||||
|
||||
// 使用 defineExpose 暴露方法
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -433,11 +459,18 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.icon-picker-content {
|
||||
height: 70vh;
|
||||
height: calc(100vh - 120px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
@ -521,6 +554,7 @@ onMounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, nextTick } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { NInput } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import IconPicker from '@/core/components/IconPicker.vue'
|
||||
@ -17,17 +17,12 @@ const iconValue = computed({
|
||||
}
|
||||
})
|
||||
|
||||
// IconPicker 组件引用(用于触发打开)
|
||||
// IconPicker 组件引用(用于调用打开方法)
|
||||
const iconPickerRef = ref<InstanceType<typeof IconPicker> | null>(null)
|
||||
|
||||
// 打开图标选择器
|
||||
async function openIconPicker() {
|
||||
await nextTick()
|
||||
// 通过点击隐藏的 IconPicker 按钮来打开弹窗
|
||||
const triggerButton = iconPickerRef.value?.$el?.querySelector('.icon-trigger') as HTMLElement
|
||||
if (triggerButton) {
|
||||
triggerButton.click()
|
||||
}
|
||||
function openIconPicker() {
|
||||
iconPickerRef.value?.open()
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -83,14 +78,14 @@ async function openIconPicker() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标选择器(隐藏触发按钮,通过自定义按钮触发)-->
|
||||
<div v-if="canEdit" style="position: absolute; left: -9999px; opacity: 0; pointer-events: none;">
|
||||
<IconPicker
|
||||
ref="iconPickerRef"
|
||||
:model-value="iconValue"
|
||||
@update:model-value="iconValue = $event"
|
||||
/>
|
||||
</div>
|
||||
<!-- 图标选择器(不渲染触发按钮,仅用于弹窗)-->
|
||||
<IconPicker
|
||||
v-if="canEdit"
|
||||
ref="iconPickerRef"
|
||||
:model-value="iconValue"
|
||||
@update:model-value="iconValue = $event"
|
||||
style="display: none;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user