@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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/dist/components/breadcrumb/index.d.ts +0 -0
  4. package/dist/components/breadcrumb/index.vue.d.ts +3 -0
  5. package/dist/components/breadcrumb/props.d.ts +0 -0
  6. package/dist/components/button/action/index.d.ts +3 -0
  7. package/dist/components/button/action/index.vue.d.ts +118 -0
  8. package/dist/components/button/action/props.d.ts +63 -0
  9. package/dist/components/button/action/utils.d.ts +8 -0
  10. package/dist/components/button/base/index.d.ts +3 -0
  11. package/dist/components/button/base/index.vue.d.ts +36 -0
  12. package/dist/components/button/base/props.d.ts +27 -0
  13. package/dist/components/button/index.d.ts +4 -0
  14. package/dist/components/button/types.d.ts +2 -0
  15. package/dist/components/form/helper.d.ts +11 -0
  16. package/dist/components/form/index.d.ts +3 -0
  17. package/dist/components/form/index.vue.d.ts +642 -0
  18. package/dist/components/form/props.d.ts +34 -0
  19. package/dist/components/icon/IconPicker.vue.d.ts +13 -0
  20. package/dist/components/icon/iconify.d.ts +25 -0
  21. package/dist/components/icon/index.d.ts +3 -0
  22. package/dist/components/icon/index.vue.d.ts +12 -0
  23. package/dist/components/layout/const.d.ts +22 -0
  24. package/dist/components/layout/context.d.ts +77 -0
  25. package/dist/components/layout/index.d.ts +5 -0
  26. package/dist/components/layout/index.vue.d.ts +80 -0
  27. package/dist/components/layout/layout-parts/AppBreadcrumb.vue.d.ts +3 -0
  28. package/dist/components/layout/layout-parts/AppFooter.vue.d.ts +18 -0
  29. package/dist/components/layout/layout-parts/AppHeader.vue.d.ts +18 -0
  30. package/dist/components/layout/layout-parts/AppLeftLogoInfo.vue.d.ts +3 -0
  31. package/dist/components/layout/layout-parts/AppMain.vue.d.ts +18 -0
  32. package/dist/components/layout/layout-parts/AppSidebar.vue.d.ts +4067 -0
  33. package/dist/components/layout/layout-parts/LayoutTransition.vue.d.ts +58 -0
  34. package/dist/components/layout/mode.d.ts +0 -0
  35. package/dist/components/layout/props.d.ts +35 -0
  36. package/dist/components/layout/types.d.ts +59 -0
  37. package/dist/components/layout/utils.d.ts +97 -0
  38. package/dist/components/provider/index.d.ts +3 -0
  39. package/dist/components/provider/index.vue.d.ts +19 -0
  40. package/dist/components/provider/props.d.ts +33 -0
  41. package/dist/components/search-bar/index.d.ts +3 -0
  42. package/dist/components/search-bar/index.vue.d.ts +1288 -0
  43. package/dist/components/search-bar/props.d.ts +15 -0
  44. package/dist/components/table/TableSetting.vue.d.ts +15 -0
  45. package/dist/components/table/index.d.ts +4 -0
  46. package/dist/components/table/index.vue.d.ts +17246 -0
  47. package/dist/components/table/props.d.ts +26 -0
  48. package/dist/components/table/useColumn.d.ts +15 -0
  49. package/dist/components/upload/enum.d.ts +18 -0
  50. package/dist/components/upload/index.d.ts +4 -0
  51. package/dist/components/upload/index.vue.d.ts +17 -0
  52. package/dist/components/upload/props.d.ts +7 -0
  53. package/dist/const/defaults.d.ts +7 -0
  54. package/dist/const/index.d.ts +2 -0
  55. package/dist/const/types.d.ts +134 -0
  56. package/dist/context/color.d.ts +13 -0
  57. package/dist/context/common.d.ts +117 -0
  58. package/dist/context/index.d.ts +41 -0
  59. package/dist/context/layout.d.ts +52 -0
  60. package/dist/context/loading-bar.d.ts +14 -0
  61. package/dist/context/locale.d.ts +143 -0
  62. package/dist/context/menu.d.ts +212 -0
  63. package/dist/context/message.d.ts +14 -0
  64. package/dist/context/notification.d.ts +14 -0
  65. package/dist/context/table.d.ts +917 -0
  66. package/dist/context/theme.d.ts +20 -0
  67. package/dist/hooks/index.d.ts +6 -0
  68. package/dist/hooks/useAdmin.d.ts +0 -0
  69. package/dist/hooks/useForm.d.ts +54 -0
  70. package/dist/hooks/useLayout.d.ts +116 -0
  71. package/dist/hooks/useProviderContext.d.ts +17 -0
  72. package/dist/hooks/useTable.d.ts +66 -0
  73. package/dist/hooks/useThemeOverrides.d.ts +8 -0
  74. package/dist/hooks/useUpload.d.ts +22 -0
  75. package/dist/index.css +36 -0
  76. package/dist/index.d.ts +29 -0
  77. package/dist/index.js +6771 -0
  78. package/dist/share/compact.d.ts +16 -0
  79. package/dist/share/index.d.ts +2 -0
  80. package/dist/share/menu.d.ts +0 -0
  81. package/dist/share/route.d.ts +0 -0
  82. package/dist/share/slot.d.ts +6 -0
  83. package/dist/utils/form.d.ts +0 -0
  84. package/dist/utils/index.d.ts +0 -0
  85. package/dist/utils/transformRoutes.d.ts +67 -0
  86. package/dist/utils/tree.d.ts +6 -0
  87. package/package.json +53 -0
  88. package/src/auto-imports.d.ts +73 -0
  89. package/src/components/breadcrumb/index.ts +0 -0
  90. package/src/components/breadcrumb/index.vue +0 -0
  91. package/src/components/breadcrumb/props.ts +0 -0
  92. package/src/components/button/action/index.ts +4 -0
  93. package/src/components/button/action/index.vue +313 -0
  94. package/src/components/button/action/props.ts +78 -0
  95. package/src/components/button/action/utils.ts +122 -0
  96. package/src/components/button/base/index.ts +4 -0
  97. package/src/components/button/base/index.vue +156 -0
  98. package/src/components/button/base/props.ts +29 -0
  99. package/src/components/button/index.ts +4 -0
  100. package/src/components/button/types.ts +2 -0
  101. package/src/components/form/helper.ts +73 -0
  102. package/src/components/form/index.ts +5 -0
  103. package/src/components/form/index.vue +243 -0
  104. package/src/components/form/props.ts +75 -0
  105. package/src/components/icon/IconPicker.vue +255 -0
  106. package/src/components/icon/iconify.ts +80 -0
  107. package/src/components/icon/index.ts +7 -0
  108. package/src/components/icon/index.vue +23 -0
  109. package/src/components/layout/const.ts +102 -0
  110. package/src/components/layout/context.ts +189 -0
  111. package/src/components/layout/index.ts +8 -0
  112. package/src/components/layout/index.vue +64 -0
  113. package/src/components/layout/layout-parts/AppBreadcrumb.vue +108 -0
  114. package/src/components/layout/layout-parts/AppFooter.vue +26 -0
  115. package/src/components/layout/layout-parts/AppHeader.vue +112 -0
  116. package/src/components/layout/layout-parts/AppLeftLogoInfo.vue +30 -0
  117. package/src/components/layout/layout-parts/AppMain.vue +34 -0
  118. package/src/components/layout/layout-parts/AppSidebar.vue +174 -0
  119. package/src/components/layout/layout-parts/LayoutTransition.vue +366 -0
  120. package/src/components/layout/mode.ts +0 -0
  121. package/src/components/layout/props.ts +36 -0
  122. package/src/components/layout/types.ts +79 -0
  123. package/src/components/layout/utils.ts +201 -0
  124. package/src/components/provider/index.ts +5 -0
  125. package/src/components/provider/index.vue +69 -0
  126. package/src/components/provider/props.ts +45 -0
  127. package/src/components/search-bar/index.ts +5 -0
  128. package/src/components/search-bar/index.vue +282 -0
  129. package/src/components/search-bar/props.ts +26 -0
  130. package/src/components/table/TableSetting.vue +253 -0
  131. package/src/components/table/index.ts +14 -0
  132. package/src/components/table/index.vue +179 -0
  133. package/src/components/table/props.ts +29 -0
  134. package/src/components/table/useColumn.ts +104 -0
  135. package/src/components/upload/enum.ts +21 -0
  136. package/src/components/upload/index.ts +9 -0
  137. package/src/components/upload/index.vue +267 -0
  138. package/src/components/upload/props.ts +8 -0
  139. package/src/components.d.ts +154 -0
  140. package/src/const/defaults.ts +94 -0
  141. package/src/const/index.ts +2 -0
  142. package/src/const/types.ts +139 -0
  143. package/src/context/color.ts +53 -0
  144. package/src/context/common.ts +27 -0
  145. package/src/context/index.ts +141 -0
  146. package/src/context/layout.ts +34 -0
  147. package/src/context/loading-bar.ts +26 -0
  148. package/src/context/locale.ts +22 -0
  149. package/src/context/menu.ts +26 -0
  150. package/src/context/message.ts +30 -0
  151. package/src/context/notification.ts +29 -0
  152. package/src/context/table.ts +32 -0
  153. package/src/context/theme.ts +35 -0
  154. package/src/hooks/index.ts +6 -0
  155. package/src/hooks/useAdmin.ts +0 -0
  156. package/src/hooks/useForm.ts +272 -0
  157. package/src/hooks/useLayout.ts +300 -0
  158. package/src/hooks/useProviderContext.ts +47 -0
  159. package/src/hooks/useTable.ts +241 -0
  160. package/src/hooks/useThemeOverrides.ts +18 -0
  161. package/src/hooks/useUpload.ts +82 -0
  162. package/src/index.ts +59 -0
  163. package/src/share/compact.ts +35 -0
  164. package/src/share/index.ts +2 -0
  165. package/src/share/menu.ts +0 -0
  166. package/src/share/route.ts +0 -0
  167. package/src/share/slot.ts +29 -0
  168. package/src/utils/form.ts +0 -0
  169. package/src/utils/index.ts +0 -0
  170. package/src/utils/transformRoutes.ts +163 -0
  171. package/src/utils/tree.ts +31 -0
@@ -0,0 +1,241 @@
1
+ import type { DataTableColumn } from 'naive-ui'
2
+ import type { QuiTable } from '../components/table'
3
+ import { NImage } from 'naive-ui'
4
+ import { h, ref } from 'vue'
5
+
6
+ interface UseTableReturn<T> {
7
+ tableRef: ReturnType<typeof ref<InstanceType<typeof QuiTable>>>
8
+ refresh: (isReset?: boolean) => void
9
+ downloadCsv: (fileName?: string) => void
10
+ setActions: (columnOption: Partial<DataTableColumn<T>>) => void
11
+ setColumns: (columns: DataTableColumn<T>[]) => void
12
+ getColumns: () => DataTableColumn<T>[] | undefined
13
+ setSize: (size: 'small' | 'medium' | 'large') => void
14
+ setStriped: (striped: boolean) => void
15
+ getPagination: () => any
16
+ setPagination: (patch: Partial<{ page: number, pageSize: number }>) => void
17
+ getData: <TRow = any>() => TRow[] | undefined
18
+ setData: (rows: T[]) => void
19
+ setFetch: (fn: (pageInfo: { pageNum: number, pageSize: number }) => Promise<{ list: T[], total: number }>) => void
20
+ withSearch: (
21
+ getSearchParams: () => Record<string, any>,
22
+ remote: (args: { pageNum: number, pageSize: number } & Record<string, any>) => Promise<{ list: T[], total: number }>
23
+ ) => void
24
+ createImageColumn: (key: keyof T & string, title?: string, width?: number) => DataTableColumn<T>
25
+ createIndexColumn: (title?: string, width?: number) => DataTableColumn<T>
26
+ createTextColumn: (key: keyof T & string, title: string, width?: number) => DataTableColumn<T>
27
+ createMoneyColumn: (key: keyof T & string, title?: string, precision?: number) => DataTableColumn<T>
28
+ }
29
+
30
+ /**
31
+ * 表格辅助函数集合
32
+ *
33
+ * 提供对 `QuiTable` 实例的常用操作封装,以及常用列的构造器。
34
+ * 适合在业务或示例中快速调用刷新、导出、追加操作列等能力。
35
+ *
36
+ * @returns 一组表格操作与便捷列构造器
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const { tableRef, refresh, downloadCsv, setActions } = useTable()
41
+ * refresh(true)
42
+ * downloadCsv('export')
43
+ * setActions({ title: '操作', render: (row) => h('div', {}, '查看') })
44
+ * ```
45
+ *
46
+ * @remarks
47
+ * - 所有实例方法均基于 `QuiTable` 的 `defineExpose` 能力安全调用
48
+ * - 列构造器仅提供基础构造,仍可按需覆盖属性
49
+ *
50
+ * @security
51
+ * - 导出文件名由调用方决定,请避免包含敏感信息
52
+ *
53
+ * @performance
54
+ * - 方法本身为轻量封装,不引入额外复杂度
55
+ */
56
+ export function useTable<T = Record<string, any>>(): UseTableReturn<T> {
57
+ const tableRef = ref<InstanceType<typeof QuiTable>>()
58
+
59
+ /**
60
+ * 刷新表格数据
61
+ *
62
+ * @param isReset - 是否重置到第一页
63
+ */
64
+ function refresh(isReset = false) {
65
+ tableRef.value?.updateTableData(isReset)
66
+ }
67
+
68
+ /**
69
+ * 导出 CSV
70
+ *
71
+ * @param fileName - 文件名(不含扩展名)
72
+ */
73
+ function downloadCsv(fileName = 'data-table') {
74
+ tableRef.value?.downloadCsv(fileName)
75
+ }
76
+
77
+ /**
78
+ * 追加操作列
79
+ *
80
+ * @param columnOption - 操作列的部分配置
81
+ */
82
+ function setActions(columnOption: Partial<DataTableColumn<T>>) {
83
+ tableRef.value?.setActions(columnOption as any)
84
+ }
85
+
86
+ /**
87
+ * 设置列集合
88
+ */
89
+ function setColumns(columns: DataTableColumn<T>[]) {
90
+ tableRef.value?.setColumns(columns as any)
91
+ }
92
+
93
+ /**
94
+ * 获取当前列集合
95
+ */
96
+ function getColumns(): DataTableColumn<T>[] | undefined {
97
+ return tableRef.value?.getColumns() as any
98
+ }
99
+
100
+ /**
101
+ * 设置尺寸
102
+ */
103
+ function setSize(size: 'small' | 'medium' | 'large') {
104
+ tableRef.value?.setSize(size as any)
105
+ }
106
+
107
+ /**
108
+ * 设置斑马纹
109
+ */
110
+ function setStriped(striped: boolean) {
111
+ tableRef.value?.setStriped(striped)
112
+ }
113
+
114
+ /**
115
+ * 获取分页信息快照
116
+ */
117
+ function getPagination() {
118
+ return tableRef.value?.getPagination()
119
+ }
120
+
121
+ /**
122
+ * 局部更新分页信息
123
+ */
124
+ function setPagination(patch: Partial<{ page: number, pageSize: number }>) {
125
+ tableRef.value?.setPagination(patch as any)
126
+ }
127
+
128
+ /**
129
+ * 获取当前数据
130
+ */
131
+ function getData<TRow = any>(): TRow[] | undefined {
132
+ return tableRef.value?.getData() as any
133
+ }
134
+
135
+ /**
136
+ * 覆盖当前数据行
137
+ */
138
+ function setData(rows: T[]) {
139
+ tableRef.value?.setData(rows as any)
140
+ }
141
+
142
+ /**
143
+ * 替换远端 fetch 函数
144
+ */
145
+ function setFetch(
146
+ fn: (pageInfo: { pageNum: number, pageSize: number }) => Promise<{ list: T[], total: number }>
147
+ ) {
148
+ tableRef.value?.setFetch(fn as any)
149
+ }
150
+
151
+ /**
152
+ * 绑定外部搜索条件到远端 fetch
153
+ *
154
+ * @param getSearchParams - 返回搜索条件对象的函数
155
+ * @param remote - 实际请求函数,接收合并后的参数
156
+ */
157
+ function withSearch(
158
+ getSearchParams: () => Record<string, any>,
159
+ remote: (args: { pageNum: number, pageSize: number } & Record<string, any>) => Promise<{ list: T[], total: number }>
160
+ ) {
161
+ setFetch(async ({ pageNum, pageSize }) => {
162
+ const params = getSearchParams() || {}
163
+ return remote({ pageNum, pageSize, ...params })
164
+ })
165
+ }
166
+
167
+ /**
168
+ * 构造图片列
169
+ *
170
+ * @param key - 数据字段
171
+ * @param title - 列标题
172
+ * @param width - 列宽
173
+ * @returns 图片列配置
174
+ */
175
+ function createImageColumn(key: keyof T & string, title = '图片', width = 80): DataTableColumn<T> {
176
+ return {
177
+ title,
178
+ key,
179
+ width,
180
+ render(row) {
181
+ return h(NImage, { width, src: (row as any)[key] })
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * 构造序号列
188
+ */
189
+ function createIndexColumn(title = '序号', width = 80): DataTableColumn<T> {
190
+ return {
191
+ title,
192
+ key: '__index__' as any,
193
+ width,
194
+ render(_: any, index: number) {
195
+ return index + 1
196
+ }
197
+ } as any
198
+ }
199
+
200
+ /**
201
+ * 构造纯文本列
202
+ */
203
+ function createTextColumn(key: keyof T & string, title: string, width?: number): DataTableColumn<T> {
204
+ return { title, key, width } as any
205
+ }
206
+
207
+ /**
208
+ * 金额列
209
+ */
210
+ function createMoneyColumn(key: keyof T & string, title = '金额', precision = 2): DataTableColumn<T> {
211
+ return {
212
+ title,
213
+ key,
214
+ render(row: T) {
215
+ const val = Number((row as any)[key] ?? 0)
216
+ return (val / 100).toFixed(precision)
217
+ }
218
+ } as any
219
+ }
220
+
221
+ return {
222
+ tableRef,
223
+ refresh,
224
+ downloadCsv,
225
+ setActions,
226
+ setColumns,
227
+ getColumns,
228
+ setSize,
229
+ setStriped,
230
+ getPagination,
231
+ setPagination,
232
+ getData,
233
+ setData,
234
+ setFetch,
235
+ withSearch,
236
+ createImageColumn,
237
+ createIndexColumn,
238
+ createTextColumn,
239
+ createMoneyColumn
240
+ }
241
+ }
@@ -0,0 +1,18 @@
1
+ import type { Ref } from 'vue'
2
+ import { lightTheme } from 'naive-ui'
3
+ import { ref } from 'vue'
4
+
5
+ /**
6
+ * 获取 Naive UI 特定组件的主题覆盖配置
7
+ * 核心价值在于提供完整的 TypeScript 类型提示,方便用户在业务代码中访问或动态计算样式。
8
+ *
9
+ */
10
+ export function useThemeOverrides<K extends keyof typeof lightTheme>(
11
+ componentName: K
12
+ ): Ref<(typeof lightTheme)[K]> {
13
+ const theme = lightTheme
14
+
15
+ const overrides = ref(theme[componentName]) as Ref<(typeof lightTheme)[K]>
16
+
17
+ return overrides
18
+ }
@@ -0,0 +1,82 @@
1
+ import type { UploadProps } from 'naive-ui'
2
+ import type { Props as QuiUploadProps } from '../components/upload/props'
3
+ import { AcceptType } from '../components/upload/enum'
4
+
5
+ /**
6
+ * 上传组件属性辅助函数
7
+ *
8
+ * 提供一组便捷的方法来生成不同类型的上传组件属性配置(如图片、视频、音频)。
9
+ *
10
+ * @param config - 基础上传配置
11
+ * @returns 包含获取不同类型上传属性的方法对象
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const { getImageUploadProps } = useUploadProps({ max: 5 })
16
+ * const props = getImageUploadProps({ action: '/upload' })
17
+ * ```
18
+ */
19
+ export function useUploadProps(config: QuiUploadProps) {
20
+ const uploadComponentProps: QuiUploadProps = {
21
+ defaultUpload: true,
22
+ max: 1,
23
+ accept: undefined,
24
+ ...config
25
+ }
26
+
27
+ /**
28
+ * 获取通用上传属性
29
+ *
30
+ * @param option - 额外的上传配置
31
+ * @returns 合并后的上传属性
32
+ */
33
+ const getUploadProps = (option?: UploadProps): UploadProps => {
34
+ return { ...uploadComponentProps, ...option }
35
+ }
36
+
37
+ /**
38
+ * 获取图片上传属性
39
+ *
40
+ * @param option - 额外的上传配置
41
+ * @returns 图片上传配置
42
+ */
43
+ const getImageUploadProps = (option?: UploadProps): UploadProps => {
44
+ return {
45
+ ...getUploadProps({ accept: AcceptType.Image }),
46
+ ...option
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 获取视频上传属性
52
+ *
53
+ * @param option - 额外的上传配置
54
+ * @returns 视频上传配置
55
+ */
56
+ const getVedioUploadProps = (option?: UploadProps): UploadProps => {
57
+ return {
58
+ ...getUploadProps({ accept: AcceptType.Video }),
59
+ ...option
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 获取音频上传属性
65
+ *
66
+ * @param option - 额外的上传配置
67
+ * @returns 音频上传配置
68
+ */
69
+ const getAudioUploadProps = (option?: UploadProps): UploadProps => {
70
+ return {
71
+ ...getUploadProps({ accept: AcceptType.Audio }),
72
+ ...option
73
+ }
74
+ }
75
+
76
+ return {
77
+ getUploadProps,
78
+ getImageUploadProps,
79
+ getVedioUploadProps,
80
+ getAudioUploadProps
81
+ }
82
+ }
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { DialogApi, LoadingBarApi, MessageApi, NotificationApi } from 'naive-ui'
2
+
3
+ import { QuiActionButton, QuiBaseButton } from './components/button'
4
+ import { QuiForm } from './components/form'
5
+ import { QuiIcon, QuiIconPicker } from './components/icon'
6
+ import { DEFAULT_LAYOUT_TYPE, QuiLayout } from './components/layout'
7
+ import { QuiProvider } from './components/provider'
8
+ import { QuiSearchBar } from './components/search-bar'
9
+ import { QuiTable } from './components/table'
10
+
11
+ import { AcceptType, QuiUpload } from './components/upload'
12
+ import 'virtual:uno.css'
13
+
14
+ export {
15
+ QuiActionButton,
16
+ QuiBaseButton,
17
+ QuiForm,
18
+ QuiIcon,
19
+ QuiIconPicker,
20
+ QuiLayout,
21
+ QuiProvider,
22
+ QuiSearchBar,
23
+ QuiTable,
24
+ QuiUpload
25
+ }
26
+
27
+ export {
28
+ AcceptType
29
+ }
30
+
31
+ export { DEFAULT_LAYOUT_TYPE }
32
+
33
+ export type { ActionItem, BaseButtonProps } from './components/button'
34
+ export type { CustomSwitchProps, FormProps, FormSchema } from './components/form'
35
+ export type { LayoutProps, LayoutType, RouteMeta } from './components/layout'
36
+
37
+ export type { ProviderProps } from './components/provider'
38
+ export type { SearchBarProps } from './components/search-bar'
39
+ export type {
40
+ TableColumn,
41
+ TableColumns,
42
+ TableExportType,
43
+ TableFetchFn,
44
+ TableProps,
45
+ TableSettings,
46
+ TableSize
47
+ } from './components/table'
48
+ export type { UploadProps } from './components/upload'
49
+ export * from './context'
50
+ export * from './hooks'
51
+
52
+ declare global {
53
+ interface Window {
54
+ $message?: MessageApi
55
+ $dialog?: DialogApi
56
+ $notification?: NotificationApi
57
+ $loadingBar?: LoadingBarApi
58
+ }
59
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 格式化像素值
3
+ *
4
+ * 将数字转换为带 px 单位的字符串,如果输入已带单位则保持不变。
5
+ *
6
+ * @param val - 数值或字符串
7
+ * @returns 格式化后的字符串或 undefined
8
+ */
9
+ export function formatPx(val?: number | string): string | undefined {
10
+ if (val === undefined || val === null || val === '')
11
+ return undefined
12
+ if (typeof val === 'string' && (val.endsWith('px') || val.endsWith('%') || val.endsWith('vh') || val.endsWith('vw') || val.endsWith('rem') || val.endsWith('em'))) {
13
+ return val
14
+ }
15
+ return `${val}px`
16
+ }
17
+
18
+ /**
19
+ * 移除对象中值为 undefined 或 null 的属性 (浅压缩)
20
+ *
21
+ * @param obj - 目标对象
22
+ * @returns 清理后的新对象
23
+ */
24
+ export function compact<T extends object>(obj: T): Partial<T> {
25
+ const result: any = {}
26
+ for (const key in obj) {
27
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
28
+ const value = obj[key]
29
+ if (value !== undefined && value !== null) {
30
+ result[key] = value
31
+ }
32
+ }
33
+ }
34
+ return result as Partial<T>
35
+ }
@@ -0,0 +1,2 @@
1
+ export * from './compact'
2
+ export * from './slot'
File without changes
File without changes
@@ -0,0 +1,29 @@
1
+ import type { Slot, VNode } from 'vue'
2
+ import { Comment, Fragment, Text } from 'vue'
3
+
4
+ /**
5
+ * 判断插槽是否有内容(忽略注释和空文本)
6
+ * @param slot - 插槽函数
7
+ */
8
+ export function hasSlotContent(slot: Slot | undefined | null): boolean {
9
+ if (!slot)
10
+ return false
11
+ const nodes = slot()
12
+ return nodes.some(isVNodeNotEmpty)
13
+ }
14
+
15
+ function isVNodeNotEmpty(vnode: VNode): boolean {
16
+ if (vnode.type === Comment)
17
+ return false
18
+ if (vnode.type === Text) {
19
+ return typeof vnode.children === 'string' && vnode.children.trim().length > 0
20
+ }
21
+ if (vnode.type === Fragment) {
22
+ if (!Array.isArray(vnode.children))
23
+ return false
24
+ if (vnode.children.length === 0)
25
+ return false
26
+ return vnode.children.some(child => isVNodeNotEmpty(child as VNode))
27
+ }
28
+ return true
29
+ }
File without changes
File without changes
@@ -0,0 +1,163 @@
1
+ import type { ComputedRef } from 'vue'
2
+ import type { RouteRecordRaw } from 'vue-router'
3
+ import type { RouteMeta } from '../components/layout/types'
4
+ import { computed } from 'vue'
5
+
6
+ export interface RouteNode {
7
+ path: string
8
+ name?: string
9
+ meta?: RouteMeta
10
+ children?: RouteNode[]
11
+ }
12
+
13
+ function joinPath(parent: string, p: string) {
14
+ const full = p?.startsWith('/') ? p : (parent ? `${parent}/${p}` : `/${p}`)
15
+ const single = full.replace(/\/+/g, '/')
16
+ return single.length > 1 && single.endsWith('/') ? single.slice(0, -1) : single
17
+ }
18
+
19
+ function sortRaw(list: RouteRecordRaw[]): RouteRecordRaw[] {
20
+ const getOrder = (r: RouteRecordRaw) => {
21
+ const m = r.meta as RouteMeta | undefined
22
+ const o = m?.order
23
+ const v = typeof o === 'number' ? o : Number(o)
24
+ return Number.isFinite(v) ? v : Number.POSITIVE_INFINITY
25
+ }
26
+ const getTitle = (r: RouteRecordRaw) => {
27
+ const m = r.meta as RouteMeta | undefined
28
+ return String(m?.title ?? r.path.split('/').filter(Boolean).pop() ?? '')
29
+ }
30
+ return [...list].sort((a, b) => {
31
+ const oa = getOrder(a)
32
+ const ob = getOrder(b)
33
+ if (oa !== ob)
34
+ return oa - ob
35
+ return getTitle(a).localeCompare(getTitle(b))
36
+ })
37
+ }
38
+
39
+ /**
40
+ * 标准化路由并自动生成重定向
41
+ *
42
+ * 递归处理路由树,拼接父子路径,并为包含子路由但未定义重定向的父路由自动生成指向第一个子路由的重定向。
43
+ *
44
+ * @param raw - 原始路由配置数组
45
+ * @param parent - 父级路径,用于递归拼接完整路径
46
+ * @returns 标准化后的路由数组,包含完整路径和自动生成的 redirect
47
+ *
48
+ * @remarks
49
+ * - 会自动拼接父级路径,确保 path 为绝对路径
50
+ * - 如果父路由没有 redirect 且有子路由,会自动将 redirect 设置为第一个子路由(优先 index 或空路径)
51
+ */
52
+ export function normalizeAndRedirect(raw: RouteRecordRaw[], parent = ''): RouteRecordRaw[] {
53
+ const childrenSorted = sortRaw(raw)
54
+ return childrenSorted.map((r): RouteRecordRaw => {
55
+ const curPathAbs = joinPath(parent, r.path)
56
+ const kidsRaw = Array.isArray(r.children) ? (r.children as RouteRecordRaw[]) : []
57
+ const kids = kidsRaw.length ? normalizeAndRedirect(kidsRaw, curPathAbs) : undefined
58
+ let redirect = r.redirect as string | undefined
59
+ if (!redirect && kids && kids.length) {
60
+ const idx = kids.find(c => c.path === 'index' || c.path === '')
61
+ const target = idx ?? kids[0]
62
+ if (target) {
63
+ redirect = joinPath(curPathAbs, target.path)
64
+ }
65
+ }
66
+ return {
67
+ ...r,
68
+ ...(kids ? { children: kids } : {}),
69
+ ...(redirect ? { redirect } : {})
70
+ } as RouteRecordRaw
71
+ })
72
+ }
73
+
74
+ /**
75
+ * 将路由配置转换为简化的路由树结构
76
+ *
77
+ * 过滤掉 hideMenu 的路由,并提取用于菜单或导航展示的关键信息。
78
+ *
79
+ * @param raw - 原始路由配置数组
80
+ * @param parent - 父级路径
81
+ * @returns 简化后的路由节点树
82
+ */
83
+ export function toRouteTree(raw: RouteRecordRaw[], parent = ''): RouteNode[] {
84
+ return raw
85
+ .filter(r => (r.meta as RouteMeta | undefined)?.hideMenu !== true)
86
+ .map<RouteNode>((r) => {
87
+ const meta = r.meta ? { ...(r.meta as RouteMeta) } : undefined
88
+ const abs = joinPath(parent, r.path)
89
+ const children = Array.isArray(r.children) ? toRouteTree(r.children as RouteRecordRaw[], abs) : []
90
+ return { path: abs, name: r.name as string | undefined, meta, children }
91
+ })
92
+ }
93
+
94
+ /**
95
+ * 对路由树进行排序
96
+ *
97
+ * 根据 meta.order (数字) 升序排序,若 order 相同则按 title 或路径名进行字母顺序排序。
98
+ *
99
+ * @param tree - 路由节点树
100
+ * @returns 排序后的新路由树
101
+ */
102
+ export function sortRouteTree(tree: RouteNode[]): RouteNode[] {
103
+ const sorted = [...tree].sort((a, b) => {
104
+ const oa = typeof a.meta?.order === 'number' ? (a.meta?.order as number) : Number(a.meta?.order)
105
+ const ob = typeof b.meta?.order === 'number' ? (b.meta?.order as number) : Number(b.meta?.order)
106
+ const av = Number.isFinite(oa) ? oa : Number.POSITIVE_INFINITY
107
+ const bv = Number.isFinite(ob) ? ob : Number.POSITIVE_INFINITY
108
+ if (av !== bv)
109
+ return av - bv
110
+ const at = String(a.meta?.title ?? a.path.split('/').filter(Boolean).pop() ?? '')
111
+ const bt = String(b.meta?.title ?? b.path.split('/').filter(Boolean).pop() ?? '')
112
+ return at.localeCompare(bt)
113
+ })
114
+ return sorted.map(n => ({
115
+ ...n,
116
+ children: n.children ? sortRouteTree(n.children) : []
117
+ }))
118
+ }
119
+
120
+ /**
121
+ * 过滤路由树
122
+ *
123
+ * 移除在 excludePaths 列表中的路径及其子路径。
124
+ *
125
+ * @param tree - 路由节点树
126
+ * @param excludePaths - 需要排除的路径列表(支持 glob 风格匹配逻辑的简化版,即前缀匹配)
127
+ * @returns 过滤后的新路由树
128
+ */
129
+ export function filterRouteTree(tree: RouteNode[], excludePaths: string[] = []): RouteNode[] {
130
+ const norm = (p: string) => {
131
+ const s = p.startsWith('/') ? p : `/${p}`
132
+ const single = s.replace(/\/+/g, '/')
133
+ return single.length > 1 && single.endsWith('/') ? single.slice(0, -1) : single
134
+ }
135
+ const excludes = excludePaths.map(norm)
136
+ const match = (p: string) => {
137
+ const np = norm(p)
138
+ return excludes.some(ex => np === ex || np.startsWith(`${ex}/`))
139
+ }
140
+ return tree
141
+ .filter(n => !match(n.path))
142
+ .map(n => ({
143
+ ...n,
144
+ children: n.children ? filterRouteTree(n.children, excludePaths) : []
145
+ }))
146
+ }
147
+
148
+ /**
149
+ * 从原始路由配置生成可用的路由树(Composition API 钩子)
150
+ *
151
+ * 组合了标准化、转换树形结构、排序和过滤等步骤,返回一个计算属性。
152
+ *
153
+ * @param raw - 原始路由配置数组
154
+ * @param option - 配置选项
155
+ * @param option.excludePaths - 需要排除的路径列表
156
+ * @returns 包含响应式路由树的对象
157
+ */
158
+ export function useRoutesTreeFromRaw(raw: RouteRecordRaw[], option?: { excludePaths?: string[] }): { routesTree: ComputedRef<RouteNode[]> } {
159
+ const normalized = normalizeAndRedirect(raw)
160
+ const baseTree = toRouteTree(normalized)
161
+ const routesTree = computed(() => sortRouteTree(filterRouteTree(baseTree, option?.excludePaths ?? [])))
162
+ return { routesTree }
163
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 递归遍历树结构,将节点的 children 字段为空数组时改为 null。
3
+ * @param arr 树节点数组
4
+ * @returns 处理后的新数组(不修改入参引用)
5
+ */
6
+ export function convertEmptyChildrenToNull<T extends Record<string, any>>(arr: T[]): T[] {
7
+ if (!Array.isArray(arr))
8
+ return []
9
+
10
+ return arr.map((item) => {
11
+ if (!item || typeof item !== 'object')
12
+ return item
13
+
14
+ const children = (item as any).children
15
+
16
+ if (!Array.isArray(children))
17
+ return item
18
+
19
+ if (children.length === 0) {
20
+ return {
21
+ ...item,
22
+ children: null
23
+ }
24
+ }
25
+
26
+ return {
27
+ ...item,
28
+ children: convertEmptyChildrenToNull(children as any[])
29
+ } as T
30
+ })
31
+ }