@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,16 @@
1
+ /**
2
+ * 格式化像素值
3
+ *
4
+ * 将数字转换为带 px 单位的字符串,如果输入已带单位则保持不变。
5
+ *
6
+ * @param val - 数值或字符串
7
+ * @returns 格式化后的字符串或 undefined
8
+ */
9
+ export declare function formatPx(val?: number | string): string | undefined;
10
+ /**
11
+ * 移除对象中值为 undefined 或 null 的属性 (浅压缩)
12
+ *
13
+ * @param obj - 目标对象
14
+ * @returns 清理后的新对象
15
+ */
16
+ export declare function compact<T extends object>(obj: T): Partial<T>;
@@ -0,0 +1,2 @@
1
+ export * from './compact';
2
+ export * from './slot';
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ import { Slot } from 'vue';
2
+ /**
3
+ * 判断插槽是否有内容(忽略注释和空文本)
4
+ * @param slot - 插槽函数
5
+ */
6
+ export declare function hasSlotContent(slot: Slot | undefined | null): boolean;
File without changes
File without changes
@@ -0,0 +1,67 @@
1
+ import { ComputedRef } from 'vue';
2
+ import { RouteRecordRaw } from 'vue-router';
3
+ import { RouteMeta } from '../components/layout/types';
4
+ export interface RouteNode {
5
+ path: string;
6
+ name?: string;
7
+ meta?: RouteMeta;
8
+ children?: RouteNode[];
9
+ }
10
+ /**
11
+ * 标准化路由并自动生成重定向
12
+ *
13
+ * 递归处理路由树,拼接父子路径,并为包含子路由但未定义重定向的父路由自动生成指向第一个子路由的重定向。
14
+ *
15
+ * @param raw - 原始路由配置数组
16
+ * @param parent - 父级路径,用于递归拼接完整路径
17
+ * @returns 标准化后的路由数组,包含完整路径和自动生成的 redirect
18
+ *
19
+ * @remarks
20
+ * - 会自动拼接父级路径,确保 path 为绝对路径
21
+ * - 如果父路由没有 redirect 且有子路由,会自动将 redirect 设置为第一个子路由(优先 index 或空路径)
22
+ */
23
+ export declare function normalizeAndRedirect(raw: RouteRecordRaw[], parent?: string): RouteRecordRaw[];
24
+ /**
25
+ * 将路由配置转换为简化的路由树结构
26
+ *
27
+ * 过滤掉 hideMenu 的路由,并提取用于菜单或导航展示的关键信息。
28
+ *
29
+ * @param raw - 原始路由配置数组
30
+ * @param parent - 父级路径
31
+ * @returns 简化后的路由节点树
32
+ */
33
+ export declare function toRouteTree(raw: RouteRecordRaw[], parent?: string): RouteNode[];
34
+ /**
35
+ * 对路由树进行排序
36
+ *
37
+ * 根据 meta.order (数字) 升序排序,若 order 相同则按 title 或路径名进行字母顺序排序。
38
+ *
39
+ * @param tree - 路由节点树
40
+ * @returns 排序后的新路由树
41
+ */
42
+ export declare function sortRouteTree(tree: RouteNode[]): RouteNode[];
43
+ /**
44
+ * 过滤路由树
45
+ *
46
+ * 移除在 excludePaths 列表中的路径及其子路径。
47
+ *
48
+ * @param tree - 路由节点树
49
+ * @param excludePaths - 需要排除的路径列表(支持 glob 风格匹配逻辑的简化版,即前缀匹配)
50
+ * @returns 过滤后的新路由树
51
+ */
52
+ export declare function filterRouteTree(tree: RouteNode[], excludePaths?: string[]): RouteNode[];
53
+ /**
54
+ * 从原始路由配置生成可用的路由树(Composition API 钩子)
55
+ *
56
+ * 组合了标准化、转换树形结构、排序和过滤等步骤,返回一个计算属性。
57
+ *
58
+ * @param raw - 原始路由配置数组
59
+ * @param option - 配置选项
60
+ * @param option.excludePaths - 需要排除的路径列表
61
+ * @returns 包含响应式路由树的对象
62
+ */
63
+ export declare function useRoutesTreeFromRaw(raw: RouteRecordRaw[], option?: {
64
+ excludePaths?: string[];
65
+ }): {
66
+ routesTree: ComputedRef<RouteNode[]>;
67
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 递归遍历树结构,将节点的 children 字段为空数组时改为 null。
3
+ * @param arr 树节点数组
4
+ * @returns 处理后的新数组(不修改入参引用)
5
+ */
6
+ export declare function convertEmptyChildrenToNull<T extends Record<string, any>>(arr: T[]): T[];
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@quiteer/naive-extra",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "",
6
+ "author": "",
7
+ "license": "MIT",
8
+ "homepage": "https://quiteerjs.github.io/web/plugins/naive-extra/",
9
+ "keywords": [],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./index.css": "./dist/index.css"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "main": "dist/index.js",
22
+ "module": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "style": "dist/index.css",
25
+ "files": [
26
+ "dist/**/*",
27
+ "src/**/*"
28
+ ],
29
+ "dependencies": {
30
+ "@vueuse/core": "^14.1.0",
31
+ "defu": "^6.1.4",
32
+ "vue-draggable-plus": "^0.6.0",
33
+ "vue-router": "^4.6.3",
34
+ "@quiteer/is": "0.0.2",
35
+ "@quiteer/unocss": "0.0.6"
36
+ },
37
+ "devDependencies": {
38
+ "csstype": "^3.2.3",
39
+ "lodash-es": "^4.17.21",
40
+ "naive-ui": "2.43.1",
41
+ "sass": "^1.94.0",
42
+ "vite": "npm:rolldown-vite@7.2.2",
43
+ "vite-plugin-dts": "^4.5.4",
44
+ "vue": "^3.5.24",
45
+ "@quiteer/vite-plugins": "^0.1.4",
46
+ "@quiteer/unocss": "0.0.6"
47
+ },
48
+ "scripts": {
49
+ "build": "vite build",
50
+ "dev": "vite build -w",
51
+ "release": "qui r --tag-prefix naive-extra && tsdown && pnpm publish --access public"
52
+ }
53
+ }
@@ -0,0 +1,73 @@
1
+ /* eslint-disable */
2
+ /* prettier-ignore */
3
+ // @ts-nocheck
4
+ // noinspection JSUnusedGlobalSymbols
5
+ // Generated by unplugin-auto-import
6
+ // biome-ignore lint: disable
7
+ export {}
8
+ declare global {
9
+ const EffectScope: typeof import('vue').EffectScope
10
+ const computed: typeof import('vue').computed
11
+ const createApp: typeof import('vue').createApp
12
+ const customRef: typeof import('vue').customRef
13
+ const defineAsyncComponent: typeof import('vue').defineAsyncComponent
14
+ const defineComponent: typeof import('vue').defineComponent
15
+ const effectScope: typeof import('vue').effectScope
16
+ const getCurrentInstance: typeof import('vue').getCurrentInstance
17
+ const getCurrentScope: typeof import('vue').getCurrentScope
18
+ const getCurrentWatcher: typeof import('vue').getCurrentWatcher
19
+ const h: typeof import('vue').h
20
+ const inject: typeof import('vue').inject
21
+ const isProxy: typeof import('vue').isProxy
22
+ const isReactive: typeof import('vue').isReactive
23
+ const isReadonly: typeof import('vue').isReadonly
24
+ const isRef: typeof import('vue').isRef
25
+ const isShallow: typeof import('vue').isShallow
26
+ const markRaw: typeof import('vue').markRaw
27
+ const nextTick: typeof import('vue').nextTick
28
+ const onActivated: typeof import('vue').onActivated
29
+ const onBeforeMount: typeof import('vue').onBeforeMount
30
+ const onBeforeUnmount: typeof import('vue').onBeforeUnmount
31
+ const onBeforeUpdate: typeof import('vue').onBeforeUpdate
32
+ const onDeactivated: typeof import('vue').onDeactivated
33
+ const onErrorCaptured: typeof import('vue').onErrorCaptured
34
+ const onMounted: typeof import('vue').onMounted
35
+ const onRenderTracked: typeof import('vue').onRenderTracked
36
+ const onRenderTriggered: typeof import('vue').onRenderTriggered
37
+ const onScopeDispose: typeof import('vue').onScopeDispose
38
+ const onServerPrefetch: typeof import('vue').onServerPrefetch
39
+ const onUnmounted: typeof import('vue').onUnmounted
40
+ const onUpdated: typeof import('vue').onUpdated
41
+ const onWatcherCleanup: typeof import('vue').onWatcherCleanup
42
+ const provide: typeof import('vue').provide
43
+ const reactive: typeof import('vue').reactive
44
+ const readonly: typeof import('vue').readonly
45
+ const ref: typeof import('vue').ref
46
+ const resolveComponent: typeof import('vue').resolveComponent
47
+ const shallowReactive: typeof import('vue').shallowReactive
48
+ const shallowReadonly: typeof import('vue').shallowReadonly
49
+ const shallowRef: typeof import('vue').shallowRef
50
+ const toRaw: typeof import('vue').toRaw
51
+ const toRef: typeof import('vue').toRef
52
+ const toRefs: typeof import('vue').toRefs
53
+ const toValue: typeof import('vue').toValue
54
+ const triggerRef: typeof import('vue').triggerRef
55
+ const unref: typeof import('vue').unref
56
+ const useAttrs: typeof import('vue').useAttrs
57
+ const useCssModule: typeof import('vue').useCssModule
58
+ const useCssVars: typeof import('vue').useCssVars
59
+ const useId: typeof import('vue').useId
60
+ const useModel: typeof import('vue').useModel
61
+ const useSlots: typeof import('vue').useSlots
62
+ const useTemplateRef: typeof import('vue').useTemplateRef
63
+ const watch: typeof import('vue').watch
64
+ const watchEffect: typeof import('vue').watchEffect
65
+ const watchPostEffect: typeof import('vue').watchPostEffect
66
+ const watchSyncEffect: typeof import('vue').watchSyncEffect
67
+ }
68
+ // for type re-export
69
+ declare global {
70
+ // @ts-ignore
71
+ export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
72
+ import('vue')
73
+ }
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ import QuiActionButton from './index.vue'
2
+
3
+ export * from './props'
4
+ export { QuiActionButton }
@@ -0,0 +1,313 @@
1
+ <script setup lang="ts">
2
+ import type { DropdownOption } from 'naive-ui'
3
+ import type { StyleValue } from 'vue'
4
+ import type { ActionButtonProps, ActionItem } from './props'
5
+ import { useElementSize, useResizeObserver } from '@vueuse/core'
6
+ import { QuiIcon } from '../../icon'
7
+ import QuiBaseButton from '../base/index.vue'
8
+ import {
9
+ resolveButtonProps as _resolveButtonProps,
10
+ resolveMoreButtonProps as _resolveMoreButtonProps,
11
+ getIcon,
12
+ isDisabled
13
+ } from './utils'
14
+
15
+ const props = withDefaults(defineProps<ActionButtonProps>(), {
16
+ actions: () => [],
17
+ mode: 'button',
18
+ max: 3,
19
+ // buttonProps 默认值留空,由内部逻辑根据 mode 计算
20
+ buttonProps: undefined,
21
+ size: undefined,
22
+ showDivider: false
23
+ })
24
+
25
+ const resolveButtonProps = (item: ActionItem) => _resolveButtonProps(item, props.mode, { ...props.buttonProps, size: props.size })
26
+ const resolveMoreButtonProps = () => _resolveMoreButtonProps(props.mode, { ...props.buttonProps, size: props.size })
27
+
28
+ // 判断是否显示
29
+ function isShow(item: ActionItem) {
30
+ if (item.show === undefined)
31
+ return true
32
+ if (typeof item.show === 'function')
33
+ return item.show()
34
+ return unref(item.show)
35
+ }
36
+
37
+ // 过滤出可见的操作
38
+ const visibleActions = computed(() => {
39
+ return props.actions.filter(isShow)
40
+ })
41
+
42
+ // 响应式显示逻辑
43
+ const containerRef = ref<any>()
44
+ const shadowRef = ref<any>()
45
+ const autoMax = ref(props.max)
46
+
47
+ const containerEl = computed(() => containerRef.value?.$el || containerRef.value)
48
+ const { width: containerWidth } = useElementSize(containerEl)
49
+
50
+ const limit = computed(() => props.responsive ? autoMax.value : props.max)
51
+
52
+ // 分割显示和折叠的操作
53
+ const displayActions = computed(() => {
54
+ return visibleActions.value.slice(0, limit.value)
55
+ })
56
+
57
+ const collapsedActions = computed(() => {
58
+ return visibleActions.value.slice(limit.value)
59
+ })
60
+
61
+ // 计算自动折叠数量
62
+ function updateAutoMax() {
63
+ const el = containerEl.value
64
+ if (!el)
65
+ return
66
+
67
+ const width = containerWidth.value
68
+ if (!width)
69
+ return
70
+
71
+ // 获取间距 (NFlex size="small" 默认为 8px gap)
72
+ const style = getComputedStyle(el)
73
+ // 优先使用 column-gap,回退到 gap
74
+ const gap = Number.parseFloat(style.columnGap || style.gap) || 8
75
+ const paddingLeft = Number.parseFloat(style.paddingLeft) || 0
76
+ const paddingRight = Number.parseFloat(style.paddingRight) || 0
77
+
78
+ // 可用宽度需要减去内边距
79
+ const availableWidth = width - paddingLeft - paddingRight
80
+
81
+ // 获取所有按钮和元素的宽度
82
+ if (!shadowRef.value)
83
+ return
84
+ const children = Array.from(shadowRef.value.children) as HTMLElement[]
85
+ // shadowRef 结构: [Btn1, Btn2, ..., MoreBtn, Divider]
86
+ // 最后一个是 Divider, 倒数第二个是 MoreBtn
87
+ const dividerEl = children[children.length - 1]
88
+ const moreBtnEl = children[children.length - 2]
89
+ const actionBtns = children.slice(0, -2)
90
+
91
+ const getWidth = (el: HTMLElement | undefined) => el?.getBoundingClientRect().width || 0
92
+
93
+ const dividerWidth = getWidth(dividerEl)
94
+ const moreBtnWidth = getWidth(moreBtnEl)
95
+
96
+ // 1. 尝试放入所有按钮
97
+ // 宽度计算:
98
+ // 无分割线: Btn + Gap + Btn + Gap ...
99
+ // 有分割线: Btn + Gap + Divider + Gap + Btn ...
100
+
101
+ const calculateWidth = (btnCount: number, hasMore: boolean) => {
102
+ if (btnCount === 0)
103
+ return hasMore ? moreBtnWidth : 0
104
+
105
+ let width = 0
106
+ // 累加按钮宽度
107
+ for (let i = 0; i < btnCount; i++) {
108
+ width += getWidth(actionBtns[i])
109
+ }
110
+
111
+ // 累加按钮之间的间距和分割线
112
+ const intervalCount = btnCount - 1
113
+ if (intervalCount > 0) {
114
+ if (props.showDivider) {
115
+ width += intervalCount * (gap * 2 + dividerWidth)
116
+ }
117
+ else {
118
+ width += intervalCount * gap
119
+ }
120
+ }
121
+
122
+ // 如果有 More 按钮
123
+ if (hasMore) {
124
+ // 连接处
125
+ // Btn (Gap [Divider Gap]) More
126
+ if (props.showDivider) {
127
+ width += gap * 2 + dividerWidth
128
+ }
129
+ else {
130
+ width += gap
131
+ }
132
+ width += moreBtnWidth
133
+ }
134
+
135
+ return width
136
+ }
137
+
138
+ // 检查是否所有都能放下 (无 More)
139
+ const allWidth = calculateWidth(actionBtns.length, false)
140
+ // 留出 1px 缓冲,避免子像素精度问题
141
+ if (allWidth <= availableWidth - 1) {
142
+ autoMax.value = actionBtns.length
143
+ return
144
+ }
145
+
146
+ // 尝试找到最大能放下的数量 (带 More)
147
+ // 从 actionBtns.length - 1 开始递减
148
+ for (let i = actionBtns.length - 1; i >= 0; i--) {
149
+ const w = calculateWidth(i, true)
150
+ if (w <= availableWidth - 1) {
151
+ autoMax.value = i
152
+ return
153
+ }
154
+ }
155
+
156
+ // 最坏情况显示 0 个 (只显示 More)
157
+ autoMax.value = 0
158
+ }
159
+
160
+ useResizeObserver(shadowRef, () => {
161
+ if (props.responsive) {
162
+ updateAutoMax()
163
+ }
164
+ })
165
+
166
+ watch([containerWidth, () => props.actions, () => props.showDivider, () => props.responsive], () => {
167
+ if (props.responsive) {
168
+ // 使用 requestAnimationFrame 避免在一次渲染周期内多次计算
169
+ requestAnimationFrame(() => updateAutoMax())
170
+ }
171
+ })
172
+
173
+ const shadowStyle: StyleValue = {
174
+ display: 'flex',
175
+ gap: '8px',
176
+ position: 'absolute',
177
+ visibility: 'hidden',
178
+ pointerEvents: 'none',
179
+ top: 0,
180
+ left: 0,
181
+ zIndex: -1,
182
+ width: 'max-content'
183
+ }
184
+
185
+ onMounted(() => {
186
+ if (props.responsive) {
187
+ updateAutoMax()
188
+ // 初始延迟再次计算,确保字体加载等
189
+ setTimeout(updateAutoMax, 100)
190
+ }
191
+ })
192
+
193
+ function handleClick(item: ActionItem) {
194
+ item.onClick?.(item.key, item)
195
+ }
196
+
197
+ function handlePopconfirmPositiveClick(item: ActionItem) {
198
+ if (item.onPositiveClick) {
199
+ item.onPositiveClick(item.key, item)
200
+ return
201
+ }
202
+
203
+ // 兼容 popconfirm 对象中的 onPositiveClick
204
+ if (typeof item.popconfirm === 'object' && item.popconfirm !== null && 'onPositiveClick' in item.popconfirm) {
205
+ (item.popconfirm as any).onPositiveClick(item.key, item)
206
+ return
207
+ }
208
+
209
+ item.onClick?.(item.key, item)
210
+ }
211
+
212
+ // 下拉菜单选项
213
+ const dropdownOptions = computed<DropdownOption[]>(() => {
214
+ return collapsedActions.value.map(item => ({
215
+ key: item.key,
216
+ label: item.label,
217
+ icon: () => {
218
+ const icon = getIcon(item)
219
+ return icon ? h(QuiIcon, { icon }) : undefined
220
+ },
221
+ disabled: isDisabled(item),
222
+ props: {
223
+ onClick: () => {
224
+ if (!item.popconfirm) {
225
+ handleClick(item)
226
+ }
227
+ }
228
+ }
229
+ }))
230
+ })
231
+
232
+ function handleDropdownSelect(key: string | number) {
233
+ const item = collapsedActions.value.find(a => a.key === key)
234
+ if (item && !item.popconfirm) {
235
+ handleClick(item)
236
+ }
237
+ }
238
+ </script>
239
+
240
+ <template>
241
+ <NFlex
242
+ ref="containerRef"
243
+ align="center"
244
+ size="small"
245
+ v-bind="$attrs"
246
+ style="width: 100%"
247
+ :wrap="false"
248
+ >
249
+ <template v-for="(item, index) in displayActions" :key="`action-${item.key}`">
250
+ <QuiBaseButton
251
+ v-bind="resolveButtonProps(item)"
252
+ @click="handleClick(item)"
253
+ @positive-click="handlePopconfirmPositiveClick(item)"
254
+ >
255
+ <template v-if="props.mode !== 'icon'">
256
+ {{ item.label }}
257
+ </template>
258
+ </QuiBaseButton>
259
+ <NDivider v-if="showDivider && index < displayActions.length - 1" :key="`div-${item.key}`" vertical />
260
+ </template>
261
+
262
+ <!-- More Button -->
263
+ <NDropdown
264
+ v-if="collapsedActions.length > 0"
265
+ :options="dropdownOptions"
266
+ @select="handleDropdownSelect"
267
+ >
268
+ <div style="display: inline-flex; align-items: center;">
269
+ <NDivider v-if="showDivider && displayActions.length > 0" vertical />
270
+ <QuiBaseButton
271
+ v-bind="resolveMoreButtonProps()"
272
+ >
273
+ <template v-if="props.mode !== 'icon'">
274
+ 更多
275
+ </template>
276
+ </QuiBaseButton>
277
+ </div>
278
+ </NDropdown>
279
+ </NFlex>
280
+
281
+ <!-- Shadow Container for Calculation -->
282
+ <div
283
+ v-if="responsive"
284
+ ref="shadowRef"
285
+ class="action-button-shadow"
286
+ :style="shadowStyle"
287
+ aria-hidden="true"
288
+ >
289
+ <QuiBaseButton
290
+ v-for="item in visibleActions"
291
+ :key="`shadow-${item.key}`"
292
+ v-bind="resolveButtonProps(item)"
293
+ style="flex-shrink: 0;"
294
+ >
295
+ <template v-if="props.mode !== 'icon'">
296
+ {{ item.label }}
297
+ </template>
298
+ </QuiBaseButton>
299
+
300
+ <!-- Shadow More Button -->
301
+ <QuiBaseButton
302
+ v-bind="resolveMoreButtonProps()"
303
+ style="flex-shrink: 0;"
304
+ >
305
+ <template v-if="props.mode !== 'icon'">
306
+ 更多
307
+ </template>
308
+ </QuiBaseButton>
309
+
310
+ <!-- Shadow Divider -->
311
+ <NDivider vertical />
312
+ </div>
313
+ </template>
@@ -0,0 +1,78 @@
1
+ import type { ButtonProps, PopconfirmProps } from 'naive-ui'
2
+ import type { Ref } from 'vue'
3
+ import type { ButtonPermission } from '../base/props'
4
+
5
+ export type ActionButtonMode = 'icon' | 'text' | 'icon-text' | 'button' | 'secondary'
6
+
7
+ export enum ActionEnum {
8
+ EDIT = 'edit',
9
+ DELETE = 'delete',
10
+ VIEW = 'view',
11
+ ADD = 'add',
12
+ SEARCH = 'search',
13
+ DOWNLOAD = 'download',
14
+ UPLOAD = 'upload',
15
+ SETTING = 'setting'
16
+ }
17
+
18
+ export interface ActionItem {
19
+ /** 唯一标识,也是默认的图标映射键值 */
20
+ key: ActionEnum | string
21
+ /** 按钮显示文本 */
22
+ label: string
23
+ /** 按钮尺寸 */
24
+ size?: ButtonProps['size']
25
+ /** 图标名称,如果不传则显示 defaultIcon */
26
+ icon?: string
27
+ /** 按钮属性 */
28
+ props?: ButtonProps
29
+ /** 点击事件 */
30
+ onClick?: (key: ActionEnum | string, item: ActionItem) => void
31
+ /** 是否显示 */
32
+ show?: boolean | (() => boolean) | Ref<boolean>
33
+ /** 是否禁用 */
34
+ disabled?: boolean | (() => boolean) | Ref<boolean>
35
+ /** 提示文本,默认使用 label */
36
+ tooltip?: string
37
+ /** 二次确认框配置,存在则开启二次确认 */
38
+ popconfirm?: PopconfirmProps
39
+ /** 确认框内容 */
40
+ popText?: string
41
+ /** 确认按钮文字 */
42
+ positiveText?: string
43
+ /** 取消按钮文字 */
44
+ negativeText?: string
45
+ /** 确认回调 */
46
+ onPositiveClick?: (key: ActionEnum | string, item: ActionItem) => void
47
+ /** 权限控制 */
48
+ permission?: ButtonPermission
49
+ }
50
+
51
+ export const DEFAULT_ACTION_ICONS: Record<string, string> = {
52
+ [ActionEnum.EDIT]: 'carbon:edit',
53
+ [ActionEnum.DELETE]: 'carbon:trash-can',
54
+ [ActionEnum.VIEW]: 'carbon:view',
55
+ [ActionEnum.ADD]: 'carbon:add',
56
+ [ActionEnum.SEARCH]: 'carbon:search',
57
+ [ActionEnum.DOWNLOAD]: 'carbon:download',
58
+ [ActionEnum.UPLOAD]: 'carbon:upload',
59
+ [ActionEnum.SETTING]: 'carbon:settings',
60
+ more: 'carbon:overflow-menu-horizontal'
61
+ }
62
+
63
+ export interface ActionButtonProps {
64
+ /** 操作按钮列表 */
65
+ actions?: ActionItem[]
66
+ /** 显示模式:纯图标 | 纯文字 | 图标+文字 */
67
+ mode?: ActionButtonMode
68
+ /** 最大显示数量,超过折叠 */
69
+ max?: number
70
+ /** 统一的按钮属性 */
71
+ buttonProps?: ButtonProps
72
+ /** 按钮尺寸 */
73
+ size?: ButtonProps['size']
74
+ /** 是否显示分割线 */
75
+ showDivider?: boolean
76
+ /** 是否开启响应式折叠,开启后 max 属性失效,根据宽度自动计算显示数量 */
77
+ responsive?: boolean
78
+ }