@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,272 @@
1
+ import type { Ref } from 'vue'
2
+ import type { FormSchema } from '../components/form/props'
3
+
4
+ interface Option { label: string, value: any, disabled?: boolean }
5
+
6
+ /**
7
+ * 基于表单 `schemas` 提供便捷的查询与更新工具
8
+ *
9
+ * 适用于外部通过接口动态配置下拉选项、默认值、组件属性等场景,
10
+ * 对传入的 `schemas` 进行就地更新(保持响应式)。
11
+ *
12
+ * @param schemas - 表单项描述数组,建议传入响应式引用
13
+ * @returns 返回一组对 `schemas` 的操作方法
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const { setOptions, setComponentProps } = useForm(schemas)
18
+ * setOptions('status', [{ label: '启用', value: 1 }])
19
+ * setComponentProps('status', { clearable: true })
20
+ * ```
21
+ *
22
+ * @remarks
23
+ * - 所有更新均为原地更新,适配 Vue 的响应式数组/对象
24
+ * - 仅对存在的字段进行更新,不会抛异常
25
+ *
26
+ * @security
27
+ * 请勿在 `componentProps` 中写入敏感信息
28
+ *
29
+ * @performance
30
+ * 操作复杂度均为 O(n) 或更低,适合一般表单规模
31
+ */
32
+ export function useForm(schemas: FormSchema[], formRef?: Ref<any>) {
33
+ /**
34
+ * 查找指定字段的 schema
35
+ *
36
+ * @param field - 字段名
37
+ * @returns 命中的 `FormSchema` 或 `undefined`
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const schema = useItemByField('status')
42
+ * ```
43
+ */
44
+ function useItemByField(field: string) {
45
+ return schemas.find(item => item.field === field)
46
+ }
47
+
48
+ /**
49
+ * 为 Select/RadioGroup/Checkbox 等具备 `options` 的组件设置选项
50
+ *
51
+ * @param field - 字段名
52
+ * @param options - 选项数组,包含 `label` 与 `value`
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * setOptions('status', [
57
+ * { label: '未上架', value: 0 },
58
+ * { label: '已上架', value: 1 }
59
+ * ])
60
+ * ```
61
+ *
62
+ * @remarks
63
+ * - 支持的组件:`NSelect`、`NRadioGroup`、`NCheckbox`
64
+ */
65
+ function setOptions(field: string, options: Option[]) {
66
+ const target = useItemByField(field)
67
+ if (!target)
68
+ return
69
+ const comp = target.component
70
+ const allow = comp === 'NSelect' || comp === 'NRadioGroup' || comp === 'NCheckbox'
71
+ if (!allow)
72
+ return
73
+ if (!target.componentProps || typeof target.componentProps !== 'object') {
74
+ target.componentProps = {}
75
+ }
76
+ target.componentProps.options = options
77
+ }
78
+
79
+ /**
80
+ * 获取指定字段的选项数组
81
+ *
82
+ * @param field - 字段名
83
+ * @returns 选项数组或 `undefined`
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const opts = getOptions('status')
88
+ * ```
89
+ */
90
+ function getOptions(field: string): Option[] | undefined {
91
+ const target = useItemByField(field)
92
+ return target?.componentProps?.options
93
+ }
94
+
95
+ /**
96
+ * 合并更新组件属性 `componentProps`
97
+ *
98
+ * @param field - 字段名
99
+ * @param props - 待合并的属性对象
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * setComponentProps('status', { clearable: true })
104
+ * ```
105
+ */
106
+ function setComponentProps(field: string, props: Record<string, any>) {
107
+ const target = useItemByField(field)
108
+ if (!target)
109
+ return
110
+ if (!target.componentProps || typeof target.componentProps !== 'object') {
111
+ target.componentProps = {}
112
+ }
113
+ Object.assign(target.componentProps, props)
114
+ }
115
+
116
+ /**
117
+ * 设置单个字段的默认值
118
+ *
119
+ * @param field - 字段名
120
+ * @param value - 默认值
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * setDefaultValue('status', 1)
125
+ * ```
126
+ */
127
+ function setDefaultValue(field: string, value: any) {
128
+ const target = useItemByField(field)
129
+ if (!target)
130
+ return
131
+ target.defaultValue = value
132
+ }
133
+
134
+ /**
135
+ * 局部更新指定字段的 schema
136
+ *
137
+ * @param field - 字段名
138
+ * @param patch - 局部更新内容
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * replaceSchema('status', { label: '上架状态', component: 'NSelect' })
143
+ * ```
144
+ */
145
+ function replaceSchema(field: string, patch: Partial<FormSchema>) {
146
+ const target = useItemByField(field)
147
+ if (!target)
148
+ return
149
+ Object.assign(target, patch)
150
+ }
151
+
152
+ /**
153
+ * 新增或替换(按 `field` 唯一)一个 schema
154
+ *
155
+ * @param schema - 完整的表单项描述
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * upsertSchema({ field: 'title', label: '标题', component: 'NInput' })
160
+ * ```
161
+ */
162
+ function upsertSchema(schema: FormSchema) {
163
+ const idx = schemas.findIndex(s => s.field === schema.field)
164
+ if (idx >= 0) {
165
+ schemas[idx] = { ...schemas[idx], ...schema }
166
+ }
167
+ else {
168
+ schemas.push(schema)
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 移除指定字段的 schema
174
+ *
175
+ * @param field - 字段名
176
+ * @returns 是否删除成功
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const ok = removeSchema('status')
181
+ * ```
182
+ */
183
+ function removeSchema(field: string): boolean {
184
+ const idx = schemas.findIndex(s => s.field === field)
185
+ if (idx < 0)
186
+ return false
187
+ schemas.splice(idx, 1)
188
+ return true
189
+ }
190
+
191
+ /**
192
+ * 列出所有字段名
193
+ *
194
+ * @returns 字段名数组
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const fields = listFields()
199
+ * ```
200
+ */
201
+ function listFields(): string[] {
202
+ return schemas.map(s => String(s.field))
203
+ }
204
+
205
+ /**
206
+ * 从 `schemas` 生成规则映射(`path -> rules[]`)
207
+ *
208
+ * @returns 规则映射对象
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * const rules = toRulesMap()
213
+ * ```
214
+ */
215
+ function toRulesMap(): Record<string, object[]> {
216
+ const acc: Record<string, object[]> = {}
217
+ schemas.forEach((s) => {
218
+ if (Array.isArray(s.rules) && s.rules.length) {
219
+ acc[String(s.field)] = s.rules
220
+ }
221
+ })
222
+ return acc
223
+ }
224
+
225
+ function getFormRef() {
226
+ return formRef?.value
227
+ }
228
+
229
+ function bindFormRef(ref: Ref<any>) {
230
+ ;(formRef as any) = ref
231
+ }
232
+
233
+ function validate(): Promise<void> | undefined {
234
+ return formRef?.value?.validate()
235
+ }
236
+
237
+ function resetFields(): Promise<void> | undefined {
238
+ return formRef?.value?.resetFields()
239
+ }
240
+
241
+ function clearValidate(): Promise<void> | undefined {
242
+ return formRef?.value?.clearValidate()
243
+ }
244
+
245
+ function getFieldsValue(): Record<string, any> | undefined {
246
+ return formRef?.value?.getFieldsValue()
247
+ }
248
+
249
+ function setFieldsValue(values: Record<string, any>): void {
250
+ formRef?.value?.setFieldsValue(values)
251
+ }
252
+
253
+ return {
254
+ useItemByField,
255
+ setOptions,
256
+ getOptions,
257
+ setComponentProps,
258
+ setDefaultValue,
259
+ replaceSchema,
260
+ upsertSchema,
261
+ removeSchema,
262
+ listFields,
263
+ toRulesMap,
264
+ getFormRef,
265
+ bindFormRef,
266
+ validate,
267
+ resetFields,
268
+ clearValidate,
269
+ getFieldsValue,
270
+ setFieldsValue
271
+ }
272
+ }
@@ -0,0 +1,300 @@
1
+ /** 提供给用户的布局上下文以及一些协助使用的函数 */
2
+
3
+ import type { MenuOption } from 'naive-ui'
4
+ import type { ComputedRef, Ref, WritableComputedRef } from 'vue'
5
+ import type { RouteRecordRaw } from 'vue-router'
6
+ import type { LayoutType, RouteMeta } from '../components/layout/types'
7
+ import { computed, isRef, onUnmounted, reactive, ref, watch } from 'vue'
8
+ import { useRoute, useRouter } from 'vue-router'
9
+ import { DEFAULT_LAYOUT_PROPS } from '../components/layout/const'
10
+ import { filterRouteTree, normalizeAndRedirect, sortRouteTree, toRouteTree } from '../utils/transformRoutes'
11
+
12
+ // 仅在此文件内用简化上下文类型,避免类型实例化过深
13
+ export interface UseLayoutContext {
14
+ type: 'left-menu' | string
15
+ bordered: boolean
16
+ inverted: boolean
17
+ isCollapsed: boolean
18
+ headerHeight: number
19
+ footerHeight: number
20
+ showFooter: boolean
21
+ siderWidth: number
22
+ siderMixedWidth: number
23
+ collapsedWidth: number
24
+ routes: RouteLike[]
25
+ activeKey: string
26
+ menuOptions: MenuOption[]
27
+ accordion: boolean
28
+ homePath?: string
29
+ excludePaths?: string[]
30
+ updateIsCollapsed: (v: boolean) => void
31
+ updateInverted: (v: boolean) => void
32
+ updateActiveKey: (v: string) => void
33
+ }
34
+
35
+ export interface UseLayoutReturn {
36
+ collapsed: Ref<boolean>
37
+ baseRoutes: any
38
+ addRoute: (route: RouteRecordRaw) => void
39
+ addRoutes: (routes: RouteRecordRaw[]) => void
40
+ removeRoute: (id: string) => void
41
+ toggle: () => void
42
+ setCollapsed: (v: boolean) => void
43
+ activeKey: Ref<string>
44
+ setActiveKey: (key: string) => void
45
+ menuOptions: ComputedRef<MenuOption[]>
46
+ context: UseLayoutContext
47
+ type: WritableComputedRef<LayoutType>
48
+ bordered: Ref<boolean>
49
+ inverted: Ref<boolean>
50
+ headerHeight: Ref<number>
51
+ footerHeight: Ref<number>
52
+ siderWidth: Ref<number>
53
+ siderMixedWidth: Ref<number>
54
+ collapsedWidth: Ref<number>
55
+ accordion: Ref<boolean>
56
+ }
57
+
58
+ /**
59
+ * 获取扁平路由列表(基于 Vue Router)
60
+ *
61
+ * 读取 `useRouter().getRoutes()` 并转换为简化的 `RouteRecordRaw[]`(扁平结构)。
62
+ * 自动补充 `meta.title` 兜底为 `name`,并过滤 `meta.showInMenu === false` 的项。
63
+ *
64
+ * @returns 扁平路由数组,适配菜单转换
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const routes = getRoutes()
69
+ * ```
70
+ */
71
+ export interface RouteLike {
72
+ path: string
73
+ name?: string
74
+ meta?: RouteMeta
75
+ children?: RouteLike[]
76
+ }
77
+
78
+ export function useActiveKey(initialKey = ''): {
79
+ activeKey: Ref<string>
80
+ setActiveKey: (key: string) => void
81
+ } {
82
+ const activeKey = ref<string>(initialKey)
83
+ const setActiveKey = (key: string) => {
84
+ activeKey.value = key
85
+ }
86
+ return { activeKey, setActiveKey }
87
+ }
88
+
89
+ /**
90
+ * 创建布局辅助方法集合
91
+ *
92
+ * 提供统一的布局状态管理(折叠、激活键)、菜单生成以及用于 provide/inject 的上下文对象构建。
93
+ *
94
+ * @param option - 布局初始化与参数配置
95
+ * @param option.routes - 路由数组或其 Ref(用于生成菜单)
96
+ * @param option.initialCollapsed - 初始折叠状态,默认 false
97
+ * @param option.initialActiveKey - 初始激活菜单键,默认 ''
98
+ * @param option.bordered - 是否显示边框,支持布尔或 Ref
99
+ * @param option.inverted - 是否启用反色样式,支持布尔或 Ref
100
+ * @param option.headerHeight - 头部高度,支持 number 或 Ref<number>
101
+ * @param option.footerHeight - 底部高度,支持 number 或 Ref<number>
102
+ * @param option.siderWidth - 侧栏宽度,支持 number 或 Ref<number>
103
+ * @param option.collapsedWidth - 折叠宽度,支持 number 或 Ref<number>
104
+ * @returns 返回包含状态控制、菜单选项与上下文的辅助对象
105
+ * @throws {TypeError} 在编译期校验参数类型,不在运行时抛错
106
+ *
107
+ * @remarks
108
+ * - 统一创建 ComputedRef 包裹的配置,确保在 provide/inject 下保持响应式
109
+ * - `routes` 支持数组或 Ref,内部自动转换为响应式
110
+ *
111
+ * @security
112
+ * - 无副作用,仅管理 UI 状态与映射
113
+ *
114
+ * @performance
115
+ * - 主要开销为菜单映射与少量 computed,通常可忽略
116
+ */
117
+
118
+ export function useLayout(option: {
119
+ baseRoutes: Readonly<Ref<RouteRecordRaw[]> | RouteRecordRaw[]>
120
+ menuOptions?: Ref<MenuOption[]> | MenuOption[]
121
+ initialCollapsed?: boolean
122
+ initialActiveKey?: string
123
+ type?: LayoutType
124
+ bordered?: boolean
125
+ inverted?: boolean
126
+ headerHeight?: number
127
+ footerHeight?: number
128
+ showFooter?: boolean
129
+ siderWidth?: number
130
+ siderMixedWidth?: number
131
+ collapsedWidth?: number
132
+ accordion?: boolean
133
+ homePath?: string
134
+ excludePaths?: string[]
135
+ }): UseLayoutReturn {
136
+ const route = useRoute()
137
+ const router = useRouter()
138
+
139
+ // 状态管理
140
+ const collapsed = ref(option.initialCollapsed ?? false)
141
+ const { activeKey, setActiveKey } = useActiveKey(option.initialActiveKey ?? option.homePath ?? '/')
142
+
143
+ // 全局布局类型(当页面没有指定 meta.layout 时使用)
144
+ const globalType = ref<LayoutType>(option.type ?? DEFAULT_LAYOUT_PROPS.type)
145
+
146
+ // 最终使用的布局类型(优先使用页面 meta.layout)
147
+ const type = computed<LayoutType>({
148
+ get: () => (route.meta.layout as LayoutType) || globalType.value,
149
+ set: (v) => {
150
+ globalType.value = v
151
+ }
152
+ })
153
+
154
+ // 配置项全部转为 ref(统一响应式)
155
+ const bordered = ref(option.bordered ?? DEFAULT_LAYOUT_PROPS.bordered)
156
+ const inverted = ref(option.inverted ?? DEFAULT_LAYOUT_PROPS.inverted)
157
+ const headerHeight = ref(option.headerHeight ?? DEFAULT_LAYOUT_PROPS.headerHeight)
158
+ const footerHeight = ref(option.footerHeight ?? DEFAULT_LAYOUT_PROPS.footerHeight)
159
+ const showFooter = ref(option.showFooter ?? DEFAULT_LAYOUT_PROPS.showFooter)
160
+ const siderWidth = ref(option.siderWidth ?? DEFAULT_LAYOUT_PROPS.siderWidth)
161
+ const siderMixedWidth = ref(option.siderMixedWidth ?? DEFAULT_LAYOUT_PROPS.siderMixedWidth)
162
+ const collapsedWidth = ref(option.collapsedWidth ?? DEFAULT_LAYOUT_PROPS.collapsedWidth)
163
+ const accordion = ref(option.accordion ?? DEFAULT_LAYOUT_PROPS.accordion)
164
+
165
+ // 基础路由处理
166
+ const baseRoutes = isRef(option.baseRoutes) ? option.baseRoutes : ref(option.baseRoutes)
167
+ const normalizedBaseRoutes = computed(() => normalizeAndRedirect(baseRoutes.value as RouteRecordRaw[]))
168
+ const routesTree = computed<RouteLike[]>(() => {
169
+ const tree = toRouteTree(normalizedBaseRoutes.value) as any
170
+ const sorted = sortRouteTree(tree)
171
+ const filtered = filterRouteTree(sorted as any, option.excludePaths ?? [])
172
+ return filtered as any
173
+ })
174
+
175
+ const menuOptions = computed<MenuOption[]>(() => {
176
+ if (option.menuOptions)
177
+ return isRef(option.menuOptions) ? option.menuOptions.value : option.menuOptions
178
+
179
+ return []
180
+ })
181
+
182
+ // ✅ 构建响应式 context:直接放 ref/computed,不要 .value!
183
+ const context = reactive({
184
+ type,
185
+ // 响应式字段(ref 会被 reactive 自动 unwrap 使用,但保持响应链接)
186
+ bordered,
187
+ inverted,
188
+ isCollapsed: collapsed,
189
+ headerHeight,
190
+ footerHeight,
191
+ showFooter,
192
+ siderWidth,
193
+ collapsedWidth,
194
+ activeKey,
195
+ menuOptions,
196
+ accordion,
197
+ routes: routesTree as unknown as any,
198
+ homePath: option.homePath,
199
+ excludePaths: option.excludePaths,
200
+
201
+ // 方法
202
+ updateIsCollapsed(v: boolean) {
203
+ collapsed.value = v
204
+ },
205
+ updateInverted(v: boolean) {
206
+ inverted.value = v
207
+ },
208
+ updateActiveKey(v: string) {
209
+ setActiveKey(v)
210
+ if (/^https?:\/\//.test(v))
211
+ return
212
+
213
+ if (v.startsWith('/')) {
214
+ router.push(v)
215
+ }
216
+ else {
217
+ router.push({ name: v })
218
+ }
219
+ }
220
+ }) as unknown as UseLayoutContext
221
+
222
+ // 监听路由变化,同步更新 activeKey
223
+ const stop = watch(
224
+ () => [route.name, route.path, route.meta.activeMenu],
225
+ ([name, path, activeMenu]) => {
226
+ setActiveKey((activeMenu as string) || (name as string) || (path as string))
227
+ },
228
+ { immediate: true }
229
+ )
230
+
231
+ const stopSync = watch(
232
+ normalizedBaseRoutes,
233
+ (list) => {
234
+ for (const r of list as RouteRecordRaw[]) {
235
+ const name = r.name as string | undefined
236
+ if (name && router.hasRoute(name)) {
237
+ router.removeRoute(name)
238
+ }
239
+ router.addRoute(r)
240
+ }
241
+ },
242
+ { immediate: true, deep: true }
243
+ )
244
+
245
+ onUnmounted(() => stop())
246
+ onUnmounted(() => stopSync())
247
+
248
+ function addRoute(route: RouteRecordRaw) {
249
+ const existing = new Set<string>(router.getRoutes().map(r => String(r.name ?? '')))
250
+ const name = route.name as string | undefined
251
+ if (name && existing.has(name))
252
+ return
253
+ const [rec] = normalizeAndRedirect([route])
254
+ if (rec) {
255
+ router.addRoute(rec)
256
+ }
257
+ }
258
+
259
+ function addRoutes(routes: RouteRecordRaw[]) {
260
+ const existing = new Set<string>(router.getRoutes().map(r => String(r.name ?? '')))
261
+ const toAdd = routes.filter((r) => {
262
+ const n = r.name as string | undefined
263
+ return !n || !existing.has(n)
264
+ })
265
+ if (!toAdd.length)
266
+ return
267
+ const recs = normalizeAndRedirect(toAdd)
268
+ for (const rec of recs) router.addRoute(rec)
269
+ }
270
+
271
+ function removeRoute(id: string) {
272
+ const name = id
273
+ if (router.hasRoute(name)) {
274
+ router.removeRoute(name)
275
+ }
276
+ }
277
+
278
+ return {
279
+ collapsed,
280
+ baseRoutes,
281
+ addRoute,
282
+ addRoutes,
283
+ removeRoute,
284
+ toggle: () => (collapsed.value = !collapsed.value),
285
+ setCollapsed: (v: boolean) => (collapsed.value = v),
286
+ activeKey,
287
+ setActiveKey,
288
+ menuOptions,
289
+ context,
290
+ type,
291
+ bordered,
292
+ inverted,
293
+ headerHeight,
294
+ footerHeight,
295
+ siderWidth,
296
+ siderMixedWidth,
297
+ collapsedWidth,
298
+ accordion
299
+ }
300
+ }
@@ -0,0 +1,47 @@
1
+ import type { NaiveExtraThemeConfig } from '../const'
2
+ import type { NaiveExtraContext } from '../context/index'
3
+ import { computed, inject, ref } from 'vue'
4
+ import { DEFAULT_THEME_CONFIG } from '../const'
5
+ import { NAIVE_EXTRA_CONTEXT_KEY } from '../context/index'
6
+
7
+ /**
8
+ * 消费主题配置上下文
9
+ *
10
+ * 为用户提供访问和更新全局配置的能力。
11
+ *
12
+ * @returns {NaiveExtraContext} 包含配置状态、合并后的配置、以及更新方法
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const { mergedConfig, updateConfig } = useProviderContext()
17
+ *
18
+ * // 更新品牌色
19
+ * updateConfig({ palette: { primary: '#ff0000' } })
20
+ * ```
21
+ */
22
+ export function useProviderContext(): NaiveExtraContext {
23
+ const context = inject(NAIVE_EXTRA_CONTEXT_KEY, null)
24
+ if (context)
25
+ return context
26
+
27
+ // 兜底逻辑
28
+ const config = ref(DEFAULT_THEME_CONFIG)
29
+ const isDark = computed(() => DEFAULT_THEME_CONFIG.themeMode === 'dark')
30
+
31
+ return {
32
+ config,
33
+ mergedConfig: computed(() => DEFAULT_THEME_CONFIG as Required<NaiveExtraThemeConfig>),
34
+ providerProps: computed(() => ({})),
35
+ loadingBarProviderProps: computed(() => ({})),
36
+ messageProviderProps: computed(() => ({})),
37
+ notificationProviderProps: computed(() => ({})),
38
+ isDark,
39
+ updateConfig: () => console.warn('[NaiveExtra] 尝试在未挂载 Provider 的情况下更新配置,操作无效。'),
40
+ setThemeMode: () => {},
41
+ toggleTheme: () => {},
42
+ setLocaleMode: () => {},
43
+ toggleLocale: () => {},
44
+ setPrimaryColor: () => {},
45
+ setBorderRadius: () => {}
46
+ }
47
+ }