Files
EmailBill/Web/src/components/IconSelector.vue
SunCheng 5e38a52e5b
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 18s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
fix: 修复 TypeScript interface 语法错误
- 将 BaseChart.vue、Icon.vue、IconSelector.vue 从 TypeScript 转换为 JavaScript
- 移除 interface 声明,改用 defineProps 对象语法
- 移除类型注解,保持 JavaScript 兼容性
- 修复 ESLint 解析错误,现在所有 lint 检查通过
2026-02-18 22:09:19 +08:00

206 lines
4.4 KiB
Vue

<template>
<PopupContainer
:show="show"
:title="title"
show-cancel-button
show-confirm-button
confirm-text="选择"
cancel-text="取消"
@update:show="emit('update:show', $event)"
@confirm="handleConfirm"
@cancel="handleCancel"
>
<div class="icon-selector">
<!-- 搜索框 -->
<van-search
v-model="searchKeyword"
placeholder="搜索图标"
:clearable="true"
@input="handleSearch"
/>
<!-- 图标列表 -->
<div
v-if="filteredIcons.length > 0"
class="icon-list"
>
<div
v-for="icon in paginatedIcons"
:key="icon.iconIdentifier"
class="icon-item"
:class="{ active: selectedIconIdentifier === icon.iconIdentifier }"
@click="handleSelectIcon(icon)"
>
<Icon
:icon-identifier="icon.iconIdentifier"
:size="32"
:color="selectedIconIdentifier === icon.iconIdentifier ? '#1989fa' : '#969799'"
/>
<span class="icon-label">{{ icon.iconName }}</span>
</div>
</div>
<!-- 无结果提示 -->
<van-empty
v-else
description="未找到匹配的图标"
/>
<!-- 分页 -->
<van-pagination
v-if="totalPages > 1"
v-model:current-page="currentPage"
:total-items="filteredIcons.length"
:items-per-page="pageSize"
class="pagination"
@change="handlePageChange"
/>
</div>
</PopupContainer>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { showToast } from 'vant'
import Icon from './Icon.vue'
import PopupContainer from './PopupContainer.vue'
const props = defineProps({
show: {
type: Boolean,
required: true
},
icons: {
type: Array,
required: true
},
title: {
type: String,
default: '选择图标'
},
defaultIconIdentifier: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:show', 'confirm', 'cancel'])
const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(20)
const selectedIconIdentifier = ref(props.defaultIconIdentifier)
// 搜索过滤
const filteredIcons = computed(() => {
if (!searchKeyword.value.trim()) {
return props.icons
}
const keyword = searchKeyword.value.toLowerCase().trim()
return props.icons.filter(icon =>
icon.iconName.toLowerCase().includes(keyword) ||
icon.collectionName.toLowerCase().includes(keyword) ||
icon.iconIdentifier.toLowerCase().includes(keyword)
)
})
// 分页
const totalPages = computed(() => Math.ceil(filteredIcons.value.length / pageSize.value))
const paginatedIcons = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredIcons.value.slice(start, end)
})
const handleSearch = () => {
currentPage.value = 1
}
const handleSelectIcon = (icon) => {
selectedIconIdentifier.value = icon.iconIdentifier
}
const handlePageChange = (page) => {
currentPage.value = page
}
const handleConfirm = () => {
if (!selectedIconIdentifier.value) {
showToast('请选择一个图标')
return
}
emit('confirm', selectedIconIdentifier.value)
handleClose()
}
const handleCancel = () => {
emit('cancel')
handleClose()
}
const handleClose = () => {
searchKeyword.value = ''
currentPage.value = 1
selectedIconIdentifier.value = props.defaultIconIdentifier
}
// 监听默认图标变化
watch(() => props.defaultIconIdentifier, (newVal) => {
selectedIconIdentifier.value = newVal
})
</script>
<style scoped lang="scss">
.icon-selector {
max-height: 70vh;
display: flex;
flex-direction: column;
.icon-list {
flex: 1;
overflow-y: auto;
max-height: 55vh;
padding: 16px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px;
border: 2px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
&:hover {
border-color: #1989fa;
background-color: #f5f5f5;
}
&.active {
border-color: #1989fa;
background-color: #e6f7ff;
}
}
.icon-label {
font-size: 12px;
color: #646464;
margin-top: 8px;
text-align: center;
}
.pagination {
padding: 16px;
border-top: 1px solid #e5e7eb;
}
}
</style>