@quiteer/naive-extra 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/components/breadcrumb/index.d.ts +0 -0
- package/dist/components/breadcrumb/index.vue.d.ts +3 -0
- package/dist/components/breadcrumb/props.d.ts +0 -0
- package/dist/components/button/action/index.d.ts +3 -0
- package/dist/components/button/action/index.vue.d.ts +118 -0
- package/dist/components/button/action/props.d.ts +63 -0
- package/dist/components/button/action/utils.d.ts +8 -0
- package/dist/components/button/base/index.d.ts +3 -0
- package/dist/components/button/base/index.vue.d.ts +36 -0
- package/dist/components/button/base/props.d.ts +27 -0
- package/dist/components/button/index.d.ts +4 -0
- package/dist/components/button/types.d.ts +2 -0
- package/dist/components/form/helper.d.ts +11 -0
- package/dist/components/form/index.d.ts +3 -0
- package/dist/components/form/index.vue.d.ts +642 -0
- package/dist/components/form/props.d.ts +34 -0
- package/dist/components/icon/IconPicker.vue.d.ts +13 -0
- package/dist/components/icon/iconify.d.ts +25 -0
- package/dist/components/icon/index.d.ts +3 -0
- package/dist/components/icon/index.vue.d.ts +12 -0
- package/dist/components/layout/const.d.ts +22 -0
- package/dist/components/layout/context.d.ts +77 -0
- package/dist/components/layout/index.d.ts +5 -0
- package/dist/components/layout/index.vue.d.ts +80 -0
- package/dist/components/layout/layout-parts/AppBreadcrumb.vue.d.ts +3 -0
- package/dist/components/layout/layout-parts/AppFooter.vue.d.ts +18 -0
- package/dist/components/layout/layout-parts/AppHeader.vue.d.ts +18 -0
- package/dist/components/layout/layout-parts/AppLeftLogoInfo.vue.d.ts +3 -0
- package/dist/components/layout/layout-parts/AppMain.vue.d.ts +18 -0
- package/dist/components/layout/layout-parts/AppSidebar.vue.d.ts +4067 -0
- package/dist/components/layout/layout-parts/LayoutTransition.vue.d.ts +58 -0
- package/dist/components/layout/mode.d.ts +0 -0
- package/dist/components/layout/props.d.ts +35 -0
- package/dist/components/layout/types.d.ts +59 -0
- package/dist/components/layout/utils.d.ts +97 -0
- package/dist/components/provider/index.d.ts +3 -0
- package/dist/components/provider/index.vue.d.ts +19 -0
- package/dist/components/provider/props.d.ts +33 -0
- package/dist/components/search-bar/index.d.ts +3 -0
- package/dist/components/search-bar/index.vue.d.ts +1288 -0
- package/dist/components/search-bar/props.d.ts +15 -0
- package/dist/components/table/TableSetting.vue.d.ts +15 -0
- package/dist/components/table/index.d.ts +4 -0
- package/dist/components/table/index.vue.d.ts +17246 -0
- package/dist/components/table/props.d.ts +26 -0
- package/dist/components/table/useColumn.d.ts +15 -0
- package/dist/components/upload/enum.d.ts +18 -0
- package/dist/components/upload/index.d.ts +4 -0
- package/dist/components/upload/index.vue.d.ts +17 -0
- package/dist/components/upload/props.d.ts +7 -0
- package/dist/const/defaults.d.ts +7 -0
- package/dist/const/index.d.ts +2 -0
- package/dist/const/types.d.ts +134 -0
- package/dist/context/color.d.ts +13 -0
- package/dist/context/common.d.ts +117 -0
- package/dist/context/index.d.ts +41 -0
- package/dist/context/layout.d.ts +52 -0
- package/dist/context/loading-bar.d.ts +14 -0
- package/dist/context/locale.d.ts +143 -0
- package/dist/context/menu.d.ts +212 -0
- package/dist/context/message.d.ts +14 -0
- package/dist/context/notification.d.ts +14 -0
- package/dist/context/table.d.ts +917 -0
- package/dist/context/theme.d.ts +20 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/useAdmin.d.ts +0 -0
- package/dist/hooks/useForm.d.ts +54 -0
- package/dist/hooks/useLayout.d.ts +116 -0
- package/dist/hooks/useProviderContext.d.ts +17 -0
- package/dist/hooks/useTable.d.ts +66 -0
- package/dist/hooks/useThemeOverrides.d.ts +8 -0
- package/dist/hooks/useUpload.d.ts +22 -0
- package/dist/index.css +36 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +6771 -0
- package/dist/share/compact.d.ts +16 -0
- package/dist/share/index.d.ts +2 -0
- package/dist/share/menu.d.ts +0 -0
- package/dist/share/route.d.ts +0 -0
- package/dist/share/slot.d.ts +6 -0
- package/dist/utils/form.d.ts +0 -0
- package/dist/utils/index.d.ts +0 -0
- package/dist/utils/transformRoutes.d.ts +67 -0
- package/dist/utils/tree.d.ts +6 -0
- package/package.json +53 -0
- package/src/auto-imports.d.ts +73 -0
- package/src/components/breadcrumb/index.ts +0 -0
- package/src/components/breadcrumb/index.vue +0 -0
- package/src/components/breadcrumb/props.ts +0 -0
- package/src/components/button/action/index.ts +4 -0
- package/src/components/button/action/index.vue +313 -0
- package/src/components/button/action/props.ts +78 -0
- package/src/components/button/action/utils.ts +122 -0
- package/src/components/button/base/index.ts +4 -0
- package/src/components/button/base/index.vue +156 -0
- package/src/components/button/base/props.ts +29 -0
- package/src/components/button/index.ts +4 -0
- package/src/components/button/types.ts +2 -0
- package/src/components/form/helper.ts +73 -0
- package/src/components/form/index.ts +5 -0
- package/src/components/form/index.vue +243 -0
- package/src/components/form/props.ts +75 -0
- package/src/components/icon/IconPicker.vue +255 -0
- package/src/components/icon/iconify.ts +80 -0
- package/src/components/icon/index.ts +7 -0
- package/src/components/icon/index.vue +23 -0
- package/src/components/layout/const.ts +102 -0
- package/src/components/layout/context.ts +189 -0
- package/src/components/layout/index.ts +8 -0
- package/src/components/layout/index.vue +64 -0
- package/src/components/layout/layout-parts/AppBreadcrumb.vue +108 -0
- package/src/components/layout/layout-parts/AppFooter.vue +26 -0
- package/src/components/layout/layout-parts/AppHeader.vue +112 -0
- package/src/components/layout/layout-parts/AppLeftLogoInfo.vue +30 -0
- package/src/components/layout/layout-parts/AppMain.vue +34 -0
- package/src/components/layout/layout-parts/AppSidebar.vue +174 -0
- package/src/components/layout/layout-parts/LayoutTransition.vue +366 -0
- package/src/components/layout/mode.ts +0 -0
- package/src/components/layout/props.ts +36 -0
- package/src/components/layout/types.ts +79 -0
- package/src/components/layout/utils.ts +201 -0
- package/src/components/provider/index.ts +5 -0
- package/src/components/provider/index.vue +69 -0
- package/src/components/provider/props.ts +45 -0
- package/src/components/search-bar/index.ts +5 -0
- package/src/components/search-bar/index.vue +282 -0
- package/src/components/search-bar/props.ts +26 -0
- package/src/components/table/TableSetting.vue +253 -0
- package/src/components/table/index.ts +14 -0
- package/src/components/table/index.vue +179 -0
- package/src/components/table/props.ts +29 -0
- package/src/components/table/useColumn.ts +104 -0
- package/src/components/upload/enum.ts +21 -0
- package/src/components/upload/index.ts +9 -0
- package/src/components/upload/index.vue +267 -0
- package/src/components/upload/props.ts +8 -0
- package/src/components.d.ts +154 -0
- package/src/const/defaults.ts +94 -0
- package/src/const/index.ts +2 -0
- package/src/const/types.ts +139 -0
- package/src/context/color.ts +53 -0
- package/src/context/common.ts +27 -0
- package/src/context/index.ts +141 -0
- package/src/context/layout.ts +34 -0
- package/src/context/loading-bar.ts +26 -0
- package/src/context/locale.ts +22 -0
- package/src/context/menu.ts +26 -0
- package/src/context/message.ts +30 -0
- package/src/context/notification.ts +29 -0
- package/src/context/table.ts +32 -0
- package/src/context/theme.ts +35 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useAdmin.ts +0 -0
- package/src/hooks/useForm.ts +272 -0
- package/src/hooks/useLayout.ts +300 -0
- package/src/hooks/useProviderContext.ts +47 -0
- package/src/hooks/useTable.ts +241 -0
- package/src/hooks/useThemeOverrides.ts +18 -0
- package/src/hooks/useUpload.ts +82 -0
- package/src/index.ts +59 -0
- package/src/share/compact.ts +35 -0
- package/src/share/index.ts +2 -0
- package/src/share/menu.ts +0 -0
- package/src/share/route.ts +0 -0
- package/src/share/slot.ts +29 -0
- package/src/utils/form.ts +0 -0
- package/src/utils/index.ts +0 -0
- package/src/utils/transformRoutes.ts +163 -0
- package/src/utils/tree.ts +31 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { GridItemProps, GridProps, SwitchProps } from 'naive-ui'
|
|
2
|
+
import type { CSSProperties } from 'vue'
|
|
3
|
+
import type { Recordable } from '../../const'
|
|
4
|
+
|
|
5
|
+
export type ComponentType
|
|
6
|
+
= | 'NInput'
|
|
7
|
+
| 'NInputGroup'
|
|
8
|
+
| 'NInputPassword'
|
|
9
|
+
| 'NInputSearch'
|
|
10
|
+
| 'NInputTextArea'
|
|
11
|
+
| 'NInputNumber'
|
|
12
|
+
| 'NInputCountDown'
|
|
13
|
+
| 'NDynamicInput'
|
|
14
|
+
| 'NSelect'
|
|
15
|
+
| 'NTreeSelect'
|
|
16
|
+
| 'NRadioButtonGroup'
|
|
17
|
+
| 'NRadioGroup'
|
|
18
|
+
| 'NCheckbox'
|
|
19
|
+
| 'NCheckboxGroup'
|
|
20
|
+
| 'NAutoComplete'
|
|
21
|
+
| 'NCascader'
|
|
22
|
+
| 'NDatePicker'
|
|
23
|
+
| 'NMonthPicker'
|
|
24
|
+
| 'NRangePicker'
|
|
25
|
+
| 'NWeekPicker'
|
|
26
|
+
| 'NTimePicker'
|
|
27
|
+
| 'NSwitch'
|
|
28
|
+
| 'NStrengthMeter'
|
|
29
|
+
| 'NUpload'
|
|
30
|
+
| 'NIconPicker'
|
|
31
|
+
| 'NRender'
|
|
32
|
+
| 'NSlider'
|
|
33
|
+
| 'NRate'
|
|
34
|
+
| 'NDynamicTags'
|
|
35
|
+
| 'NImage'
|
|
36
|
+
|
|
37
|
+
export interface FormSchema<T = Recordable<string>> {
|
|
38
|
+
field: keyof T
|
|
39
|
+
label: string
|
|
40
|
+
labelMessage?: string
|
|
41
|
+
labelMessageStyle?: object | string
|
|
42
|
+
defaultValue?: any
|
|
43
|
+
component?: ComponentType
|
|
44
|
+
componentProps?: Record<string, any>
|
|
45
|
+
slot?: string
|
|
46
|
+
rules?: object[]
|
|
47
|
+
giProps?: GridItemProps
|
|
48
|
+
isFull?: boolean
|
|
49
|
+
suffix?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Props {
|
|
53
|
+
schemas: FormSchema<Recordable<string>>[]
|
|
54
|
+
disabled?: boolean
|
|
55
|
+
// 标签宽度 固定宽度
|
|
56
|
+
labelWidth?: number | string
|
|
57
|
+
labelPlacement?: 'top' | 'left'
|
|
58
|
+
// 是否展示为行内表单
|
|
59
|
+
inline?: boolean
|
|
60
|
+
// 大小
|
|
61
|
+
size?: 'small' | 'medium' | 'large'
|
|
62
|
+
// 组件是否width 100%
|
|
63
|
+
isFull?: boolean
|
|
64
|
+
// grid 配置
|
|
65
|
+
gridProps?: GridProps
|
|
66
|
+
// grid 样式
|
|
67
|
+
gridStyle?: CSSProperties
|
|
68
|
+
// gi配置
|
|
69
|
+
giProps?: GridItemProps
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CustomSwitchProps extends SwitchProps {
|
|
73
|
+
checkedText: string
|
|
74
|
+
uncheckedText: string
|
|
75
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { getCollections, getIconNames, searchIcons } from './iconify'
|
|
3
|
+
import QuiIcon from './index.vue'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
modelValue?: string // 格式: "collection:name"
|
|
7
|
+
pageSize?: number // 每页加载数量
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{
|
|
11
|
+
(e: 'update:modelValue', value: string): void
|
|
12
|
+
(e: 'select', value: string): void
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const searchQuery = ref('')
|
|
16
|
+
const selectedCollection = ref<string>('mdi')
|
|
17
|
+
const currentPage = ref(1)
|
|
18
|
+
const hasMore = ref(true)
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
|
|
21
|
+
const collections = ref<string[]>([])
|
|
22
|
+
const allIcons = ref<{ collection: string, name: string }[]>([])
|
|
23
|
+
const displayedIcons = ref<{ collection: string, name: string }[]>([])
|
|
24
|
+
|
|
25
|
+
// 缓存各集合图标名
|
|
26
|
+
const iconsCache = new Map<string, string[]>()
|
|
27
|
+
|
|
28
|
+
const collectionOptions = computed(() => {
|
|
29
|
+
return collections.value.map(col => ({ label: col, value: col }))
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
async function initCollections() {
|
|
33
|
+
const cols = await getCollections()
|
|
34
|
+
collections.value = cols
|
|
35
|
+
if (cols.includes('mdi')) {
|
|
36
|
+
selectedCollection.value = 'mdi'
|
|
37
|
+
}
|
|
38
|
+
else if (cols.length > 0) {
|
|
39
|
+
selectedCollection.value = cols[0] || ''
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function loadPage(page: number, reset = false) {
|
|
44
|
+
loading.value = true
|
|
45
|
+
if (reset) {
|
|
46
|
+
currentPage.value = 1
|
|
47
|
+
displayedIcons.value = []
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
currentPage.value = page
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const query = searchQuery.value.trim().toLowerCase()
|
|
55
|
+
|
|
56
|
+
// 如果是搜索模式
|
|
57
|
+
if (query) {
|
|
58
|
+
if (reset) {
|
|
59
|
+
// 执行搜索
|
|
60
|
+
allIcons.value = await searchIcons(query)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 如果是浏览模式
|
|
64
|
+
else {
|
|
65
|
+
// 检查缓存
|
|
66
|
+
const currentCol = selectedCollection.value
|
|
67
|
+
let names = iconsCache.get(currentCol)
|
|
68
|
+
|
|
69
|
+
if (!names) {
|
|
70
|
+
names = await getIconNames(currentCol)
|
|
71
|
+
iconsCache.set(currentCol, names)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (reset) {
|
|
75
|
+
allIcons.value = names.map(name => ({ collection: currentCol, name }))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 分页逻辑
|
|
80
|
+
const pageSize = props.pageSize || 200
|
|
81
|
+
const start = (currentPage.value - 1) * pageSize
|
|
82
|
+
const end = start + pageSize
|
|
83
|
+
|
|
84
|
+
const pageIcons = allIcons.value.slice(start, end)
|
|
85
|
+
|
|
86
|
+
if (reset) {
|
|
87
|
+
displayedIcons.value = pageIcons
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
displayedIcons.value.push(...pageIcons)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
hasMore.value = end < allIcons.value.length
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('Failed to load icons:', error)
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
loading.value = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let searchTimeout: any
|
|
104
|
+
|
|
105
|
+
function handleSearchInput() {
|
|
106
|
+
clearTimeout(searchTimeout)
|
|
107
|
+
searchTimeout = setTimeout(() => {
|
|
108
|
+
currentPage.value = 1
|
|
109
|
+
loadPage(1, true)
|
|
110
|
+
}, 500)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleCollectionChange() {
|
|
114
|
+
searchQuery.value = '' // 切换集合时清空搜索
|
|
115
|
+
loadPage(1, true)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 滚动到底部加载更多
|
|
119
|
+
function handleScroll(e: Event) {
|
|
120
|
+
const target = e.target as HTMLElement
|
|
121
|
+
if (target.scrollTop + target.clientHeight >= target.scrollHeight - 100) {
|
|
122
|
+
if (hasMore.value && !loading.value) {
|
|
123
|
+
currentPage.value++
|
|
124
|
+
loadPage(currentPage.value)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 选择图标
|
|
130
|
+
function selectIcon(collection: string, name: string) {
|
|
131
|
+
const iconId = `${collection}:${name}`
|
|
132
|
+
emit('update:modelValue', iconId)
|
|
133
|
+
emit('select', iconId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
onMounted(async () => {
|
|
137
|
+
await initCollections()
|
|
138
|
+
loadPage(1, true)
|
|
139
|
+
})
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<template>
|
|
143
|
+
<NCard
|
|
144
|
+
class="icon-picker w-full mx-auto h-[600px]"
|
|
145
|
+
content-style="padding: 0; display: flex; flex-direction: column; height: 100%; overflow: hidden;"
|
|
146
|
+
:bordered="true"
|
|
147
|
+
>
|
|
148
|
+
<!-- 搜索 & 集合筛选 -->
|
|
149
|
+
<div class="p-3 border-b border-gray-200 dark:border-gray-700">
|
|
150
|
+
<n-grid :cols="24" :x-gap="12">
|
|
151
|
+
<n-gi :span="16">
|
|
152
|
+
<NInput
|
|
153
|
+
v-model:value="searchQuery"
|
|
154
|
+
placeholder="搜索图标 (e.g. home, user)"
|
|
155
|
+
clearable
|
|
156
|
+
@input="handleSearchInput"
|
|
157
|
+
>
|
|
158
|
+
<template #prefix>
|
|
159
|
+
<QuiIcon icon="carbon:search" class="text-gray-400" />
|
|
160
|
+
</template>
|
|
161
|
+
</NInput>
|
|
162
|
+
</n-gi>
|
|
163
|
+
<n-gi :span="8">
|
|
164
|
+
<NSelect
|
|
165
|
+
v-model:value="selectedCollection"
|
|
166
|
+
:options="collectionOptions"
|
|
167
|
+
filterable
|
|
168
|
+
placeholder="选择集合"
|
|
169
|
+
:disabled="!!searchQuery"
|
|
170
|
+
@update:value="handleCollectionChange"
|
|
171
|
+
/>
|
|
172
|
+
</n-gi>
|
|
173
|
+
</n-grid>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<!-- 图标网格 -->
|
|
177
|
+
<div class="flex-1 overflow-hidden relative">
|
|
178
|
+
<NScrollbar
|
|
179
|
+
class="h-full"
|
|
180
|
+
content-class="p-4"
|
|
181
|
+
@scroll="handleScroll"
|
|
182
|
+
>
|
|
183
|
+
<div v-if="displayedIcons.length">
|
|
184
|
+
<n-grid
|
|
185
|
+
cols="8 s:10 m:12 l:16 xl:20"
|
|
186
|
+
responsive="screen"
|
|
187
|
+
:x-gap="8"
|
|
188
|
+
:y-gap="8"
|
|
189
|
+
>
|
|
190
|
+
<n-gi
|
|
191
|
+
v-for="item in displayedIcons"
|
|
192
|
+
:key="`${item.collection}:${item.name}`"
|
|
193
|
+
>
|
|
194
|
+
<NTooltip trigger="hover">
|
|
195
|
+
<template #trigger>
|
|
196
|
+
<div
|
|
197
|
+
class="aspect-square flex items-center justify-center rounded cursor-pointer transition-colors border border-transparent hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-200 dark:hover:border-gray-600"
|
|
198
|
+
:class="{ 'bg-primary-50 text-primary-600 border-primary-200': modelValue === `${item.collection}:${item.name}` }"
|
|
199
|
+
@click="selectIcon(item.collection, item.name)"
|
|
200
|
+
>
|
|
201
|
+
<QuiIcon
|
|
202
|
+
:icon="`${item.collection}:${item.name}`"
|
|
203
|
+
size="24"
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
</template>
|
|
207
|
+
{{ item.name }}
|
|
208
|
+
</NTooltip>
|
|
209
|
+
</n-gi>
|
|
210
|
+
</n-grid>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<!-- 空状态 -->
|
|
214
|
+
<div v-if="!loading && !displayedIcons.length" class="h-full flex items-center justify-center min-h-[200px]">
|
|
215
|
+
<NEmpty description="未找到图标" />
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<!-- 底部加载中 -->
|
|
219
|
+
<div v-if="loading && displayedIcons.length > 0" class="py-4 flex justify-center w-full">
|
|
220
|
+
<NSpin size="small" />
|
|
221
|
+
</div>
|
|
222
|
+
</NScrollbar>
|
|
223
|
+
|
|
224
|
+
<!-- 初始加载遮罩 -->
|
|
225
|
+
<div v-if="loading && displayedIcons.length === 0" class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80 z-10">
|
|
226
|
+
<NSpin size="large" />
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<!-- 当前选中预览 -->
|
|
231
|
+
<div v-if="modelValue" class="p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/50">
|
|
232
|
+
<n-flex justify="space-between" align="center">
|
|
233
|
+
<n-flex align="center" :size="8">
|
|
234
|
+
<span class="text-sm text-gray-500">已选:</span>
|
|
235
|
+
<div class="flex items-center gap-2 bg-white dark:bg-gray-800 px-2 py-1 rounded border border-gray-200 dark:border-gray-700">
|
|
236
|
+
<QuiIcon :icon="modelValue" size="20" />
|
|
237
|
+
<NText code class="text-sm">
|
|
238
|
+
{{ modelValue }}
|
|
239
|
+
</NText>
|
|
240
|
+
</div>
|
|
241
|
+
</n-flex>
|
|
242
|
+
|
|
243
|
+
<NTag size="small" type="success" :bordered="false">
|
|
244
|
+
{{ modelValue.split(':')[0] }}
|
|
245
|
+
</NTag>
|
|
246
|
+
</n-flex>
|
|
247
|
+
</div>
|
|
248
|
+
</NCard>
|
|
249
|
+
</template>
|
|
250
|
+
|
|
251
|
+
<style scoped>
|
|
252
|
+
.icon-picker {
|
|
253
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
254
|
+
}
|
|
255
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取所有图标集合
|
|
3
|
+
*
|
|
4
|
+
* 从 Iconify API 获取可用的图标集合列表
|
|
5
|
+
*
|
|
6
|
+
* @returns {Promise<string[]>} 集合前缀列表
|
|
7
|
+
*/
|
|
8
|
+
export async function getCollections(): Promise<string[]> {
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch('https://api.iconify.design/collections')
|
|
11
|
+
const data = await response.json()
|
|
12
|
+
return Object.keys(data)
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error('Failed to fetch collections:', error)
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 获取指定集合的所有图标名称
|
|
22
|
+
*
|
|
23
|
+
* @param collection - 集合前缀
|
|
24
|
+
* @returns {Promise<string[]>} 图标名称列表
|
|
25
|
+
*/
|
|
26
|
+
export async function getIconNames(collection: string): Promise<string[]> {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(`https://api.iconify.design/collection?prefix=${collection}`)
|
|
29
|
+
const data = await response.json()
|
|
30
|
+
|
|
31
|
+
const names = new Set<string>()
|
|
32
|
+
|
|
33
|
+
// 从 uncategorized 获取
|
|
34
|
+
if (data.uncategorized) {
|
|
35
|
+
data.uncategorized.forEach((name: string) => names.add(name))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 从 categories 获取
|
|
39
|
+
if (data.categories) {
|
|
40
|
+
Object.values(data.categories).forEach((categoryIcons: any) => {
|
|
41
|
+
categoryIcons.forEach((name: string) => names.add(name))
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 兜底:如果上面都没找到,尝试从 icons 对象键名获取
|
|
46
|
+
if (names.size === 0 && data.icons) {
|
|
47
|
+
Object.keys(data.icons).forEach(name => names.add(name))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Array.from(names).sort()
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(`Failed to fetch icons for ${collection}:`, error)
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 搜索图标
|
|
60
|
+
*
|
|
61
|
+
* @param query - 搜索关键词
|
|
62
|
+
* @returns {Promise<{collection: string, name: string}[]>} 图标列表
|
|
63
|
+
*/
|
|
64
|
+
export async function searchIcons(query: string): Promise<{ collection: string, name: string }[]> {
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(`https://api.iconify.design/search?query=${encodeURIComponent(query)}&limit=999`)
|
|
67
|
+
const data = await response.json()
|
|
68
|
+
if (data.icons) {
|
|
69
|
+
return data.icons.map((fullIcon: string) => {
|
|
70
|
+
const [collection, name] = fullIcon.split(':')
|
|
71
|
+
return { collection, name }
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Failed to search icons:', error)
|
|
78
|
+
return []
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Icon } from '@iconify/vue'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
/**
|
|
6
|
+
* Iconify 图标名称,格式:`collection:name`
|
|
7
|
+
* 例如:'mdi:home', 'carbon:user', 'tabler:settings'
|
|
8
|
+
*/
|
|
9
|
+
icon?: string
|
|
10
|
+
size?: string | number
|
|
11
|
+
color?: string
|
|
12
|
+
}>()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<Icon
|
|
17
|
+
v-if="icon"
|
|
18
|
+
:icon="icon"
|
|
19
|
+
:width="size"
|
|
20
|
+
:height="size"
|
|
21
|
+
:color="color"
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Props } from './props'
|
|
2
|
+
import type { LayoutType } from './types'
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_LAYOUT_PROPS: Required<Omit<Props, 'baseRoutes'>> = {
|
|
5
|
+
/** @description 布局类型 */
|
|
6
|
+
type: 'side-menu',
|
|
7
|
+
/** @description 是否显示边框 */
|
|
8
|
+
bordered: true,
|
|
9
|
+
/** @description 反色样式(Header/Sider/Footer/Menu) */
|
|
10
|
+
inverted: false,
|
|
11
|
+
/** @description 是否折叠侧边栏 */
|
|
12
|
+
isCollapsed: false,
|
|
13
|
+
/** @description 是否显示底部 */
|
|
14
|
+
showFooter: true,
|
|
15
|
+
/** @description 头部高度 */
|
|
16
|
+
headerHeight: 54,
|
|
17
|
+
/** @description 底部高度 */
|
|
18
|
+
footerHeight: 42,
|
|
19
|
+
/** @description 底部是否占满宽度 */
|
|
20
|
+
footerFull: true,
|
|
21
|
+
/** @description 侧边栏宽度 */
|
|
22
|
+
siderWidth: 220,
|
|
23
|
+
/** @description 侧边栏混合模式的宽度 */
|
|
24
|
+
siderMixedWidth: 80,
|
|
25
|
+
/** @description 折叠侧边栏宽度 */
|
|
26
|
+
collapsedWidth: 60,
|
|
27
|
+
/** @description 当前激活的路由键 */
|
|
28
|
+
activeKey: '/',
|
|
29
|
+
/** @description 菜单路由 */
|
|
30
|
+
menuOptions: [],
|
|
31
|
+
/** @description 是否开启手风琴模式 */
|
|
32
|
+
accordion: true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_LAYOUT_TYPE: { type: LayoutType, name: string, desc: string }[] = [
|
|
36
|
+
{
|
|
37
|
+
type: 'side-menu',
|
|
38
|
+
name: '左侧菜单布局',
|
|
39
|
+
desc: '左侧多层级菜单布局,顶部为面包屑'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'side-group-menu',
|
|
43
|
+
name: '左侧分组菜单布局',
|
|
44
|
+
desc: '左侧多层级菜单布局,分组展示,顶部为面包屑'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'side-mixed-menu',
|
|
48
|
+
name: '左侧混合菜单布局',
|
|
49
|
+
desc: '左侧多层级菜单布局,第一级菜单可折叠,其余层级为标题和分组展示,顶部为面包屑'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'side-menu/2',
|
|
53
|
+
name: '左侧-顶部菜单布局',
|
|
54
|
+
desc: '左侧单层级菜单布局,顶部为水平多层级菜单,无面包屑'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'top-menu',
|
|
58
|
+
name: '顶部菜单布局',
|
|
59
|
+
desc: '无侧边栏,顶部为水平多层级菜单,无面包屑'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'top-menu/2',
|
|
63
|
+
name: '顶部-左侧菜单布局',
|
|
64
|
+
desc: '顶部菜单为主,顶部为水平单层级菜单,左侧为垂直多层级菜单,无面包屑'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'top-group-menu/2',
|
|
68
|
+
name: '顶部-左侧分组菜单布局',
|
|
69
|
+
desc: '顶部菜单为主,顶部为水平单层级菜单,左侧为垂直多层级菜单,分组展示,无面包屑'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'top-mixed-menu/2',
|
|
73
|
+
name: '顶部-左侧混合菜单布局',
|
|
74
|
+
desc: '顶部菜单为主,顶部为水平单层级菜单,左侧为垂直多层级菜单,左侧第一级菜单可折叠,其余层级为标题和分组展示,无面包屑'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'blank',
|
|
78
|
+
name: '无菜单布局',
|
|
79
|
+
desc: '无菜单布局,页面不包含任何主导航菜单,适用于登录页、引导页、全屏应用等场景'
|
|
80
|
+
}
|
|
81
|
+
] as const
|
|
82
|
+
|
|
83
|
+
/** @description 所有布局类型 */
|
|
84
|
+
export const ALL_LAYOUT_TYPES: LayoutType[] = DEFAULT_LAYOUT_TYPE.map(item => item.type)
|
|
85
|
+
|
|
86
|
+
/** @description 侧边栏布局类型 */
|
|
87
|
+
export const SIDE_LAYOUT_TYPES: LayoutType[] = ['side-menu', 'side-menu/2', 'side-group-menu', 'side-mixed-menu', 'top-menu/2', 'top-group-menu/2', 'top-mixed-menu/2']
|
|
88
|
+
|
|
89
|
+
/** @description 侧边栏分组布局类型 */
|
|
90
|
+
export const SIDE_GROUP_LAYOUT_TYPES: LayoutType[] = ['side-group-menu', 'top-group-menu/2']
|
|
91
|
+
|
|
92
|
+
/** @description 侧边栏混合布局类型 */
|
|
93
|
+
export const SIDE_MIXED_LAYOUT_TYPES: LayoutType[] = ['side-mixed-menu', 'top-mixed-menu/2']
|
|
94
|
+
|
|
95
|
+
/** @description 顶部布局类型 */
|
|
96
|
+
export const TOP_LAYOUT_TYPES: LayoutType[] = ['side-menu/2', 'top-menu', 'top-menu/2', 'top-group-menu/2', 'top-mixed-menu/2']
|
|
97
|
+
|
|
98
|
+
/** @description 面包屑布局类型 */
|
|
99
|
+
export const BREADCRUMB_LAYOUT_TYPES: LayoutType[] = ['side-menu', 'side-group-menu', 'side-mixed-menu']
|
|
100
|
+
|
|
101
|
+
/** @description 无菜单布局类型 */
|
|
102
|
+
export const BLANK_LAYOUT_TYPES: LayoutType[] = ['blank']
|