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