整合前端仓库并保留提交历史
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="icon-selector">
|
||||
<div
|
||||
class="select"
|
||||
@click="handleClick"
|
||||
:style="{ width: props.width }"
|
||||
:class="[size, { 'is-disabled': disabled }, { 'has-icon': selectValue }]"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon v-show="selectValue" :icon="selectValue" />
|
||||
</div>
|
||||
<div class="text">{{ props.text }}</div>
|
||||
<div class="arrow">
|
||||
<Icon icon="ant-design:down-outlined" class="arrow-icon" />
|
||||
<Icon icon="ant-design:close-outlined" @click.stop="clearIcon" class="clear-icon" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog title="选择图标" width="40%" v-model="visible" align-center>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane v-for="prefix in iconifyPrefixs" :key="prefix" :label="prefix" :name="prefix">
|
||||
<el-scrollbar height="400px">
|
||||
<el-input v-model="searchText" placeholder="请输入图标名称"></el-input>
|
||||
<ul class="icons-list" style="margin-top: 10px">
|
||||
<li
|
||||
v-for="icon in paginatedIconifyList"
|
||||
:key="icon"
|
||||
@click="selectorIconifyIcon(icon)"
|
||||
>
|
||||
<i class="iconfont-sys">
|
||||
<Icon :icon="icon" />
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
<el-pagination
|
||||
v-if="filteredIconifyList.length > iconifyPageSize"
|
||||
layout="prev, pager, next"
|
||||
:total="filteredIconifyList.length"
|
||||
:page-size="iconifyPageSize"
|
||||
v-model:current-page="iconifyCurrentPage"
|
||||
size="small"
|
||||
style="margin-top: 10px; justify-content: center"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Svg图标" name="svg">
|
||||
<el-scrollbar height="400px">
|
||||
<ul class="icons-list">
|
||||
<li v-for="icon in svgList" :key="icon" @click="selectorIconifyIcon(icon)">
|
||||
<i class="iconfont-sys">
|
||||
<Icon :icon="icon" />
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="visible = false">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listIcons } from '@iconify/vue'
|
||||
import { fetchIconsData } from '~/utils/icon-utils'
|
||||
import Icon from './iconify.vue'
|
||||
|
||||
defineOptions({ name: 'ArtIconSelector' })
|
||||
|
||||
const searchText = ref('')
|
||||
const activeTab = ref('ant-design')
|
||||
const iconifyCurrentPage = ref(1)
|
||||
const iconifyPageSize = ref(60)
|
||||
|
||||
const svgList = computed(() => {
|
||||
return listIcons('', 'svg')
|
||||
})
|
||||
|
||||
const iconifyMap = reactive<any>({})
|
||||
const iconifyPrefixs = ['ant-design', 'carbon', 'ic', 'lucide', 'ep', 'ri']
|
||||
|
||||
const iconifyList = computed(() => {
|
||||
return iconifyMap[activeTab.value]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
iconifyPrefixs.forEach((prefix: any) => {
|
||||
fetchIconsData(prefix).then((icons) => {
|
||||
iconifyMap[prefix] = icons
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
watchDebounced(
|
||||
() => activeTab.value,
|
||||
() => {
|
||||
if (iconifyPrefixs.includes(activeTab.value)) {
|
||||
fetchIconsData(activeTab.value).then((icons) => {
|
||||
iconifyMap[activeTab.value] = icons
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const filteredIconifyList = computed<string[]>(() => {
|
||||
if (!searchText.value) {
|
||||
return iconifyList.value || []
|
||||
}
|
||||
return (iconifyList.value || []).filter((icon: any) => icon.includes(searchText.value))
|
||||
})
|
||||
|
||||
const paginatedIconifyList = computed<string[]>(() => {
|
||||
const start = (iconifyCurrentPage.value - 1) * iconifyPageSize.value
|
||||
const end = start + iconifyPageSize.value
|
||||
return filteredIconifyList.value.slice(start, end)
|
||||
})
|
||||
|
||||
type ComponentSize = 'large' | 'default' | 'small'
|
||||
|
||||
interface Props {
|
||||
iconType?: any
|
||||
modelValue?: string
|
||||
text?: string
|
||||
width?: string
|
||||
size?: ComponentSize
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
'update:modelValue': [value: string]
|
||||
getIcon: [value: string]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
iconType: '',
|
||||
modelValue: '',
|
||||
text: '图标选择器',
|
||||
width: '200px',
|
||||
size: 'default',
|
||||
disabled: false
|
||||
})
|
||||
|
||||
const emits = defineEmits<Emits>()
|
||||
|
||||
const selectValue = ref<string>(props.modelValue)
|
||||
const visible = ref<boolean>(false)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal: string) => {
|
||||
selectValue.value = newVal
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(searchText, () => {
|
||||
iconifyCurrentPage.value = 1
|
||||
})
|
||||
|
||||
const selectorIconifyIcon = (icon: string): void => {
|
||||
selectValue.value = icon
|
||||
visible.value = false
|
||||
emits('update:modelValue', icon)
|
||||
emits('getIcon', icon)
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
visible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const clearIcon = () => {
|
||||
selectValue.value = ''
|
||||
emits('update:modelValue', '')
|
||||
emits('getIcon', '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: var(--el-fill-color-blank);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.has-icon {
|
||||
.text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
&.default {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icons-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 8px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.iconfont-sys {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
# 图标组件使用说明
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持多种图标库:Ant Design、Element Plus、Carbon、IC、Lucide、Ri 等
|
||||
- 支持自定义图标
|
||||
- 支持图标选择器组件
|
||||
- 支持离线模式
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pnpm add @iconify/vue @iconify/json
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 基础图标组件 (Iconify)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Iconify icon="ant-design:home-outlined" />
|
||||
<Iconify icon="ep:home-filled" />
|
||||
<Iconify icon="carbon:home" />
|
||||
<Iconify icon="ic:baseline-home" />
|
||||
<Iconify icon="lucide:home" />
|
||||
<Iconify icon="ri:home-line" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Iconify from '~/components/iconify.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 自定义尺寸和颜色
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Iconify icon="ant-design:home-outlined" width="24" height="24" />
|
||||
<Iconify icon="ant-design:home-outlined" width="32" height="32" color="#409eff" />
|
||||
<Iconify icon="ant-design:home-outlined" size="1.5em" color="#67c23a" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3. 图标选择器 (IconSelector)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<IconSelector v-model="selectedIcon" text="选择图标" width="300px" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import IconSelector from '~/components/icon-selector.vue'
|
||||
|
||||
const selectedIcon = ref('')
|
||||
</script>
|
||||
```
|
||||
|
||||
### 4. 监听图标选择事件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<IconSelector v-model="selectedIcon" @get-icon="handleGetIcon" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import IconSelector from '~/components/icon-selector.vue'
|
||||
|
||||
const selectedIcon = ref('')
|
||||
|
||||
const handleGetIcon = (icon: string) => {
|
||||
console.log('选择的图标:', icon)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 支持的图标库
|
||||
|
||||
| 图标库 | 前缀 | 示例 |
|
||||
|--------|------|------|
|
||||
| Ant Design | `ant-design` | `ant-design:home-outlined` |
|
||||
| Element Plus | `ep` | `ep:home-filled` |
|
||||
| Carbon | `carbon` | `carbon:home` |
|
||||
| IC | `ic` | `ic:baseline-home` |
|
||||
| Lucide | `lucide` | `lucide:home` |
|
||||
| Ri | `ri` | `ri:home-line` |
|
||||
|
||||
## 自定义添加图标库
|
||||
|
||||
### 1. 安装图标库
|
||||
|
||||
```bash
|
||||
pnpm add @iconify-json/your-icon-library
|
||||
```
|
||||
|
||||
### 2. 在 load-icon.ts 中添加
|
||||
|
||||
```typescript
|
||||
import YourIcons from '@iconify/json/json/your-icon-library.json'
|
||||
addCollection(YourIcons)
|
||||
```
|
||||
|
||||
### 3. 在 icon-selector.vue 中添加
|
||||
|
||||
```typescript
|
||||
const iconifyPrefixs = ['ant-design', 'carbon', 'ic', 'lucide', 'ep', 'ri', 'your-icon-library']
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 离线模式
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```env
|
||||
VITE_APP_ICON_OFFLINE=true
|
||||
VITE_APP_ICONIFY_PREFIX=ant-design,carbon,ic,lucide,ep,ri
|
||||
```
|
||||
|
||||
### 图标选择器配置
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| modelValue | string | '' | v-model 绑定的图标值 |
|
||||
| text | string | '图标选择器' | 显示文本 |
|
||||
| width | string | '200px' | 组件宽度 |
|
||||
| size | string | 'default' | 组件大小:large/default/small |
|
||||
| disabled | boolean | false | 是否禁用 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 图标名称格式:`图标库名称:图标名称`
|
||||
2. 离线模式下需要预先加载所有图标数据
|
||||
3. 图标选择器支持搜索功能
|
||||
4. 图标选择器支持分页显示
|
||||
|
||||
## 示例代码
|
||||
|
||||
完整示例请参考项目中的使用场景。
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<i class="iconfont-sys" v-if="iconType === 'unicode'" v-html="icon"></i>
|
||||
<i :class="`iconfont-sys ${icon}`" v-else-if="iconType === 'className'"></i>
|
||||
<ElIcon class="m-icon" v-else-if="typeof icon === 'string'">
|
||||
<Icon :icon="icon" :width="width" :height="height" :color="color"></Icon>
|
||||
</ElIcon>
|
||||
<ElIcon class="m-icon" v-else-if="typeof icon === 'object'"
|
||||
><component :is="icon" :width="width" :height="height" :color="color"></component
|
||||
></ElIcon>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { ElIcon } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: [String, Object] as PropType<string | VNode>
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '1em'
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '1em'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
})
|
||||
|
||||
const iconType = computed(() => {
|
||||
if (props.icon?.toString().startsWith('iconsys-')) {
|
||||
return 'className'
|
||||
} else if (props.icon?.toString().includes(':')) {
|
||||
return 'iconify'
|
||||
}
|
||||
return 'unicode'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.m-icon {
|
||||
width: auto !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as Icon } from './iconify.vue'
|
||||
export { default as IconSelector } from './icon-selector.vue'
|
||||
export { default } from './iconify.vue'
|
||||
Reference in New Issue
Block a user