@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,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>;
|
|
File without changes
|
|
File without changes
|
|
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
|
+
};
|
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,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
|
+
}
|