@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,201 @@
|
|
|
1
|
+
import type { MenuOption } from 'naive-ui'
|
|
2
|
+
import { h } from 'vue'
|
|
3
|
+
import { RouterLink } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
type AnyMenuOption = MenuOption & { key?: string | number, children?: AnyMenuOption[], href?: string }
|
|
6
|
+
|
|
7
|
+
export function renderMenuLabel(option: AnyMenuOption) {
|
|
8
|
+
const { label, key, children, href } = option
|
|
9
|
+
|
|
10
|
+
// 如果有子菜单,直接渲染文本,不进行跳转
|
|
11
|
+
if (children && children.length > 0) {
|
|
12
|
+
return label as string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (href) {
|
|
16
|
+
return h('a', { href, target: '_blank', rel: 'noopener noreferrer' }, label as string)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const k = typeof key === 'string' ? key : String(key)
|
|
20
|
+
|
|
21
|
+
if (/^https?:\/\//.test(k)) {
|
|
22
|
+
return h('a', { href: k, target: '_blank', rel: 'noopener noreferrer' }, label as string)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return h(RouterLink, { to: { name: k } }, { default: () => label })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 查找指定 key 的菜单节点
|
|
29
|
+
export function findNodeByKey(options: AnyMenuOption[], key: string | number): AnyMenuOption | null {
|
|
30
|
+
const stack: AnyMenuOption[] = [...(options || [])]
|
|
31
|
+
while (stack.length) {
|
|
32
|
+
const n = stack.pop() as AnyMenuOption
|
|
33
|
+
if (n?.key === key)
|
|
34
|
+
return n
|
|
35
|
+
if (Array.isArray(n?.children))
|
|
36
|
+
stack.push(...n.children!)
|
|
37
|
+
}
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 返回从当前节点出发的第一个叶子节点
|
|
42
|
+
export function findFirstLeaf(node: AnyMenuOption | null | undefined): AnyMenuOption | null {
|
|
43
|
+
let cur = node ?? null
|
|
44
|
+
while (cur && Array.isArray(cur.children) && cur.children.length) {
|
|
45
|
+
cur = cur.children[0] ?? null
|
|
46
|
+
}
|
|
47
|
+
return cur ?? null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 解析 activeKey 对应的末端(叶子)key */
|
|
51
|
+
export function resolveLeafKeyFromMenu(options: AnyMenuOption[], activeKey: string | number): string {
|
|
52
|
+
const node = findNodeByKey(options, activeKey)
|
|
53
|
+
const leaf = findFirstLeaf(node)
|
|
54
|
+
const k = leaf?.key
|
|
55
|
+
return typeof k === 'string' ? k : k != null ? String(k) : ''
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 从子节点 key 解析顶层父级 key */
|
|
59
|
+
export function resolveTopParentKeyFromMenu(options: AnyMenuOption[], childKey: string | number): string {
|
|
60
|
+
const stack: Array<{ node: AnyMenuOption, topKey: string | number | undefined }> = []
|
|
61
|
+
for (const o of options || []) stack.push({ node: o as AnyMenuOption, topKey: o.key })
|
|
62
|
+
while (stack.length) {
|
|
63
|
+
const { node, topKey } = stack.pop()!
|
|
64
|
+
if (node?.key === childKey) {
|
|
65
|
+
return typeof topKey === 'string' ? topKey : topKey != null ? String(topKey) : ''
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(node?.children)) {
|
|
68
|
+
for (const c of node.children!) stack.push({ node: c, topKey })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return '/'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 判断 key 是否属于顶层菜单
|
|
75
|
+
export function isTopLevelKey(options: AnyMenuOption[], key: string | number): boolean {
|
|
76
|
+
const topKeys = (options || []).map(o => o.key)
|
|
77
|
+
const ks = topKeys.filter(k => k != null).map(k => (typeof k === 'string' ? k : String(k)))
|
|
78
|
+
const target = typeof key === 'string' ? key : String(key)
|
|
79
|
+
return ks.includes(target)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 判断 key 是否为叶子(无子节点)
|
|
83
|
+
export function isLeafKey(options: AnyMenuOption[], key: string | number): boolean {
|
|
84
|
+
const node = findNodeByKey(options, key)
|
|
85
|
+
return !!node && (!Array.isArray(node.children) || node.children.length === 0)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 从 activeKey 解析出主菜单与子菜单的键
|
|
90
|
+
*
|
|
91
|
+
* 解析传入的活跃键在菜单树中的位置,并分别返回顶层父级的 key(主菜单)
|
|
92
|
+
* 与该分支上的末端叶子 key(子菜单)。
|
|
93
|
+
*
|
|
94
|
+
* @param options - 菜单树,需包含 `key` 与可选的 `children`
|
|
95
|
+
* @param activeKey - 当前激活的菜单键(路径)
|
|
96
|
+
* @returns 返回对象 `{ mainKey, subKey }`,可能为 `null` 值
|
|
97
|
+
* @throws {TypeError} 当 `options` 非数组或 `activeKey` 非字符串/数字时在编译期提示
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const { mainKey, subKey } = resolveMainSubFromActive(menuOptions, '/dashboard/analysis')
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* - mainKey 与 subKey 均进行字符串化处理,确保比较与 includes 一致性
|
|
106
|
+
*
|
|
107
|
+
* @security
|
|
108
|
+
* - 纯函数,无副作用
|
|
109
|
+
*
|
|
110
|
+
* @performance
|
|
111
|
+
* - O(n) 遍历一次菜单树
|
|
112
|
+
*/
|
|
113
|
+
export function resolveMainSubFromActive(
|
|
114
|
+
options: AnyMenuOption[],
|
|
115
|
+
activeKey: string | number
|
|
116
|
+
): { mainKey: string | null, subKey: string | null } {
|
|
117
|
+
const mainKey = resolveTopParentKeyFromMenu(options, activeKey)
|
|
118
|
+
const subKey = resolveLeafKeyFromMenu(options, activeKey)
|
|
119
|
+
return { mainKey, subKey }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 合并主/子菜单键为最终 activeKey
|
|
124
|
+
*
|
|
125
|
+
* 当存在子菜单键时优先返回子菜单键;否则返回主菜单分支上的第一个叶子键,
|
|
126
|
+
* 若主菜单键也不存在则返回 `null`。
|
|
127
|
+
*
|
|
128
|
+
* @param options - 菜单树,需包含 `key` 与可选的 `children`
|
|
129
|
+
* @param mainKey - 顶层父级 key(主菜单)
|
|
130
|
+
* @param subKey - 末端叶子 key(子菜单)
|
|
131
|
+
* @returns 最终用于路由跳转的 activeKey(字符串)或 `null`
|
|
132
|
+
* @throws {TypeError} 当参数类型不符合预期在编译期提示
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const active = resolveActiveKeyFromPair(menuOptions, '/dashboard', '/dashboard/analysis')
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* - 若 `subKey` 不存在,将尝试从 `mainKey` 分支解析第一个叶子以保证跳转到可达页面
|
|
141
|
+
*
|
|
142
|
+
* @security
|
|
143
|
+
* - 纯函数,无副作用
|
|
144
|
+
*
|
|
145
|
+
* @performance
|
|
146
|
+
* - O(n)(在需要从 `mainKey` 分支解析叶子时)
|
|
147
|
+
*/
|
|
148
|
+
export function resolveActiveKeyFromPair(
|
|
149
|
+
options: AnyMenuOption[],
|
|
150
|
+
mainKey: string | number | null | undefined,
|
|
151
|
+
subKey: string | number | null | undefined
|
|
152
|
+
): string | null {
|
|
153
|
+
const s = subKey == null ? null : (typeof subKey === 'string' ? subKey : String(subKey))
|
|
154
|
+
if (s)
|
|
155
|
+
return s
|
|
156
|
+
const m = mainKey == null ? null : (typeof mainKey === 'string' ? mainKey : String(mainKey))
|
|
157
|
+
if (!m)
|
|
158
|
+
return null
|
|
159
|
+
return resolveLeafKeyFromMenu(options, m) ?? m
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 递归更新菜单树的每个节点
|
|
164
|
+
*
|
|
165
|
+
* 对传入的菜单树做一次深度遍历,并对每个节点应用 `updater`,返回新的树结构。
|
|
166
|
+
* 该方法默认以不可变方式工作:不会修改原始节点引用,而是为每个节点创建浅拷贝并递归重建 children。
|
|
167
|
+
*
|
|
168
|
+
* @param options - 菜单树数组(MenuOption[]),允许为空数组
|
|
169
|
+
* @param updater - 节点更新函数,接收当前节点并返回更新后的节点
|
|
170
|
+
* @returns 新的菜单树数组(保持树形结构)
|
|
171
|
+
*
|
|
172
|
+
* @throws {TypeError} 当 updater 不是函数时抛出
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* const next = updateMenuTree(menuOptions, (item) => {
|
|
177
|
+
* if (typeof item.key === 'string')
|
|
178
|
+
* return { ...item, key: item.key.replace('/old', '/new') }
|
|
179
|
+
* return item
|
|
180
|
+
* })
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function updateMenuTree(
|
|
184
|
+
options: AnyMenuOption[],
|
|
185
|
+
updater: (item: AnyMenuOption) => AnyMenuOption
|
|
186
|
+
): AnyMenuOption[] {
|
|
187
|
+
if (typeof updater !== 'function')
|
|
188
|
+
throw new TypeError('updater must be a function')
|
|
189
|
+
|
|
190
|
+
const walk = (list: AnyMenuOption[]): AnyMenuOption[] => {
|
|
191
|
+
return (list || []).map((raw) => {
|
|
192
|
+
const nextChildren = Array.isArray(raw?.children) ? walk(raw.children!) : undefined
|
|
193
|
+
const cloned: AnyMenuOption = nextChildren
|
|
194
|
+
? { ...(raw as AnyMenuOption), children: nextChildren }
|
|
195
|
+
: { ...(raw as AnyMenuOption) }
|
|
196
|
+
return updater(cloned)
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return walk(options || [])
|
|
201
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ProviderProps } from './props'
|
|
3
|
+
import { initNaiveTheme } from '@quiteer/unocss/provide'
|
|
4
|
+
import { useDialog, useLoadingBar, useMessage, useNotification } from 'naive-ui'
|
|
5
|
+
import { computed, createTextVNode, defineComponent, watch } from 'vue'
|
|
6
|
+
import { createProviderContext } from '../../context/index'
|
|
7
|
+
|
|
8
|
+
defineOptions({
|
|
9
|
+
name: 'QuiProvider'
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const props = defineProps<ProviderProps>()
|
|
13
|
+
|
|
14
|
+
// 创建并整合主题上下文
|
|
15
|
+
const { providerProps: internalProviderProps, updateConfig } = createProviderContext(props.config)
|
|
16
|
+
|
|
17
|
+
// 监听外部传入的 config 变化并同步到 context
|
|
18
|
+
watch(() => props.config, (newConfig) => {
|
|
19
|
+
if (newConfig) {
|
|
20
|
+
updateConfig(newConfig)
|
|
21
|
+
}
|
|
22
|
+
}, { deep: true })
|
|
23
|
+
|
|
24
|
+
onMounted(() => {
|
|
25
|
+
initNaiveTheme()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// 合并外部传入的 configProviderProps
|
|
29
|
+
const mergedConfigProviderProps = computed(() => ({
|
|
30
|
+
...internalProviderProps.value,
|
|
31
|
+
...props.configProviderProps
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
const ContextHolder = defineComponent({
|
|
35
|
+
name: 'ContextHolder',
|
|
36
|
+
setup() {
|
|
37
|
+
function register() {
|
|
38
|
+
window.$loadingBar = useLoadingBar()
|
|
39
|
+
window.$dialog = useDialog()
|
|
40
|
+
window.$message = useMessage()
|
|
41
|
+
window.$notification = useNotification()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
register()
|
|
45
|
+
|
|
46
|
+
return () => createTextVNode()
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<n-config-provider
|
|
53
|
+
class="h-full"
|
|
54
|
+
v-bind="mergedConfigProviderProps"
|
|
55
|
+
>
|
|
56
|
+
<n-loading-bar-provider v-bind="props.loadingBarProviderProps">
|
|
57
|
+
<n-dialog-provider v-bind="props.dialogProviderProps">
|
|
58
|
+
<n-notification-provider v-bind="props.notificationProviderProps">
|
|
59
|
+
<n-message-provider v-bind="props.messageProviderProps">
|
|
60
|
+
<ContextHolder />
|
|
61
|
+
<slot />
|
|
62
|
+
</n-message-provider>
|
|
63
|
+
</n-notification-provider>
|
|
64
|
+
</n-dialog-provider>
|
|
65
|
+
</n-loading-bar-provider>
|
|
66
|
+
</n-config-provider>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<style scoped></style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConfigProviderProps,
|
|
3
|
+
DialogProviderProps,
|
|
4
|
+
LoadingBarProviderProps,
|
|
5
|
+
MessageProviderProps,
|
|
6
|
+
NotificationProviderProps
|
|
7
|
+
} from 'naive-ui'
|
|
8
|
+
import type { NaiveExtraThemeConfig } from '../../const'
|
|
9
|
+
|
|
10
|
+
export interface ProviderProps {
|
|
11
|
+
/**
|
|
12
|
+
* NaiveExtra 主题配置
|
|
13
|
+
*/
|
|
14
|
+
config?: NaiveExtraThemeConfig
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Config Provider Props
|
|
18
|
+
* 透传 NConfigProvider 的所有属性 (优先级高于 config)
|
|
19
|
+
*/
|
|
20
|
+
configProviderProps?: ConfigProviderProps
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Loading Bar Provider Props
|
|
24
|
+
* 透传 NLoadingBarProvider 的所有属性
|
|
25
|
+
*/
|
|
26
|
+
loadingBarProviderProps?: LoadingBarProviderProps
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Dialog Provider Props
|
|
30
|
+
* 透传 NDialogProvider 的所有属性
|
|
31
|
+
*/
|
|
32
|
+
dialogProviderProps?: DialogProviderProps
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Notification Provider Props
|
|
36
|
+
* 透传 NNotificationProvider 的所有属性
|
|
37
|
+
*/
|
|
38
|
+
notificationProviderProps?: NotificationProviderProps
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Message Provider Props
|
|
42
|
+
* 透传 NMessageProvider 的所有属性
|
|
43
|
+
*/
|
|
44
|
+
messageProviderProps?: MessageProviderProps
|
|
45
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 引入 async-validator 类型以为声明生成提供稳定引用,避免 TS2742。
|
|
4
|
+
* 仅类型导入,不会影响运行时体积或行为。
|
|
5
|
+
*/
|
|
6
|
+
import type {} from 'async-validator'
|
|
7
|
+
import type { ButtonProps } from 'naive-ui'
|
|
8
|
+
import type { Recordable } from '../../const'
|
|
9
|
+
import type { Props } from './props'
|
|
10
|
+
import { isNullOrUnDef } from '@quiteer/is'
|
|
11
|
+
import { computed, nextTick, onMounted, ref, toRaw, unref, useAttrs, watch, watchEffect } from 'vue'
|
|
12
|
+
import { QuiForm } from '../form'
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
title: '搜索',
|
|
16
|
+
labelWidth: 80,
|
|
17
|
+
labelPlacement: 'top',
|
|
18
|
+
layout: 'inline',
|
|
19
|
+
inline: false,
|
|
20
|
+
size: 'medium',
|
|
21
|
+
isFull: true,
|
|
22
|
+
showActionButtonGroup: true,
|
|
23
|
+
showResetButton: true,
|
|
24
|
+
resetButtonOptions: undefined,
|
|
25
|
+
showSubmitButton: true,
|
|
26
|
+
submitButtonOptions: undefined,
|
|
27
|
+
showAdvancedButton: true,
|
|
28
|
+
expanded: undefined,
|
|
29
|
+
submitButtonText: '查询',
|
|
30
|
+
resetButtonText: '重置',
|
|
31
|
+
gridProps: undefined
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
'reset': []
|
|
36
|
+
'submit': [data: any]
|
|
37
|
+
'update:expanded': [value: boolean]
|
|
38
|
+
}>()
|
|
39
|
+
|
|
40
|
+
const slots = defineSlots()
|
|
41
|
+
|
|
42
|
+
const attrs = useAttrs()
|
|
43
|
+
|
|
44
|
+
const formProps = computed(() => {
|
|
45
|
+
return { ...toRaw(props), ...attrs }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const formRef = ref<InstanceType<typeof QuiForm>>()
|
|
49
|
+
const loadingSub = ref(false)
|
|
50
|
+
|
|
51
|
+
const submitBtnProps = computed((): ButtonProps => {
|
|
52
|
+
return {
|
|
53
|
+
size: props.size,
|
|
54
|
+
type: 'primary',
|
|
55
|
+
...props.submitButtonOptions
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const resetBtnProps = computed((): ButtonProps => {
|
|
60
|
+
return {
|
|
61
|
+
size: props.size,
|
|
62
|
+
type: 'default',
|
|
63
|
+
...props.resetButtonOptions
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 提交搜索请求
|
|
69
|
+
*
|
|
70
|
+
* 校验内部表单并返回过滤后的字段值,同时触发外部 `submit` 事件。
|
|
71
|
+
*
|
|
72
|
+
* @param e - 原生点击/提交事件
|
|
73
|
+
* @returns 成功时返回过滤后的表单数据对象;失败或未挂载时返回 `false`
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* await handleSubmit()
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* - 内部调用 `QuiForm.validate()` 保证校验一致性
|
|
82
|
+
* - 使用 `loadingSub` 控制提交按钮 loading 状态
|
|
83
|
+
*/
|
|
84
|
+
async function handleSubmit(e?: Event): Promise<object | boolean> {
|
|
85
|
+
if (e) {
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
}
|
|
88
|
+
loadingSub.value = true
|
|
89
|
+
|
|
90
|
+
const formEl = unref(formRef)
|
|
91
|
+
if (!formEl)
|
|
92
|
+
return false
|
|
93
|
+
try {
|
|
94
|
+
await formEl.validate()
|
|
95
|
+
const values = formEl.getFieldsValue()
|
|
96
|
+
loadingSub.value = false
|
|
97
|
+
emit('submit', values)
|
|
98
|
+
return values
|
|
99
|
+
}
|
|
100
|
+
catch (error: any) {
|
|
101
|
+
emit('submit', null)
|
|
102
|
+
loadingSub.value = false
|
|
103
|
+
console.error(error)
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 重置搜索条件
|
|
110
|
+
*
|
|
111
|
+
* 调用内部表单的重置逻辑,并根据 `submitOnReset` 配置决定是否立即提交一次。
|
|
112
|
+
*
|
|
113
|
+
* @returns 无返回值
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* resetFields()
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @remarks
|
|
121
|
+
* - 当 `submitOnReset` 为 `true` 时,重置后调用 `handleSubmit`
|
|
122
|
+
*/
|
|
123
|
+
async function resetFields(): Promise<void> {
|
|
124
|
+
await formRef.value?.resetFields()
|
|
125
|
+
if (props.submitOnReset) {
|
|
126
|
+
await handleSubmit()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const expandedNames = ref<string[]>([])
|
|
131
|
+
|
|
132
|
+
const isControlledExpanded = computed(() => typeof props.expanded !== 'undefined')
|
|
133
|
+
|
|
134
|
+
watch(
|
|
135
|
+
() => props.expanded,
|
|
136
|
+
(val) => {
|
|
137
|
+
if (typeof val !== 'undefined') {
|
|
138
|
+
expandedNames.value = val ? ['search'] : []
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{ immediate: true }
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
watch(
|
|
145
|
+
expandedNames,
|
|
146
|
+
(names) => {
|
|
147
|
+
emit('update:expanded', !!names.length)
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 展开搜索栏
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* expandedBar()
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
function expandedBar() {
|
|
160
|
+
expandedNames.value = ['search']
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 收起搜索栏
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* closeBar()
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
function closeBar() {
|
|
172
|
+
expandedNames.value = []
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 表单重置事件处理
|
|
177
|
+
*
|
|
178
|
+
* 透传外部 `reset` 事件,并按需触发提交。
|
|
179
|
+
*/
|
|
180
|
+
async function onFormReset() {
|
|
181
|
+
emit('reset')
|
|
182
|
+
if (props.submitOnReset) {
|
|
183
|
+
await handleSubmit()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
onMounted(() => {
|
|
188
|
+
if (!props.showAdvancedButton && !isControlledExpanded.value) {
|
|
189
|
+
expandedBar()
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
watchEffect(() => {
|
|
194
|
+
if (props.showAdvancedButton === false && !isControlledExpanded.value) {
|
|
195
|
+
expandedBar()
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 获取去除空值的表单字段
|
|
201
|
+
*
|
|
202
|
+
* 过滤 `null/undefined` 字段,避免将无效条件提交至后端。
|
|
203
|
+
*
|
|
204
|
+
* @returns 仅包含有效值的表单对象
|
|
205
|
+
*/
|
|
206
|
+
function getFilteredFieldsValue() {
|
|
207
|
+
const state = formRef.value?.getFieldsValue() ?? {}
|
|
208
|
+
const formState = {} as typeof state
|
|
209
|
+
|
|
210
|
+
for (const key in state) {
|
|
211
|
+
if (!isNullOrUnDef(state[key])) {
|
|
212
|
+
formState[key] = state[key]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return formState
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
defineExpose({
|
|
219
|
+
getFormRef: () => formRef.value,
|
|
220
|
+
getFieldsValue: getFilteredFieldsValue,
|
|
221
|
+
submit: handleSubmit,
|
|
222
|
+
reset: resetFields,
|
|
223
|
+
expandedBar,
|
|
224
|
+
closeBar,
|
|
225
|
+
/**
|
|
226
|
+
* 设置搜索数据并自动展开
|
|
227
|
+
*
|
|
228
|
+
* @param data - 需要写入的搜索条件键值对
|
|
229
|
+
*/
|
|
230
|
+
setSearchData: async (data: Recordable) => {
|
|
231
|
+
expandedBar()
|
|
232
|
+
await nextTick()
|
|
233
|
+
formRef.value?.setFieldsValue(data)
|
|
234
|
+
},
|
|
235
|
+
resetFields
|
|
236
|
+
})
|
|
237
|
+
</script>
|
|
238
|
+
|
|
239
|
+
<template>
|
|
240
|
+
<NCard size="small">
|
|
241
|
+
<NCollapse v-model:expanded-names="expandedNames" class="select-none" display-directive="show">
|
|
242
|
+
<template v-if="showAdvancedButton" #header-extra>
|
|
243
|
+
<NFlex align="center" :gap="0" class="text-purple-8">
|
|
244
|
+
{{ expandedNames.length ? '关闭' : '展开' }}
|
|
245
|
+
<i v-if="!expandedNames.length" class="i-mdi-arrow-up-down-bold" />
|
|
246
|
+
<i v-else class="i-mdi-arrow-collapse-vertical" />
|
|
247
|
+
</NFlex>
|
|
248
|
+
</template>
|
|
249
|
+
<NCollapseItem :title="title" name="search">
|
|
250
|
+
<QuiForm ref="formRef" v-bind="formProps" size="small" @reset="onFormReset">
|
|
251
|
+
<template v-for="(_, key, i) in slots" :key="i" #[key]="{ model, field, value }">
|
|
252
|
+
<slot :name="key" :model="model" :field="field" :value="value" />
|
|
253
|
+
</template>
|
|
254
|
+
<template #action-button>
|
|
255
|
+
<NButton
|
|
256
|
+
v-if="showSubmitButton"
|
|
257
|
+
size="small"
|
|
258
|
+
v-bind="submitBtnProps"
|
|
259
|
+
:loading="loadingSub"
|
|
260
|
+
attr-type="submit"
|
|
261
|
+
secondary
|
|
262
|
+
@click="handleSubmit"
|
|
263
|
+
>
|
|
264
|
+
<template #icon>
|
|
265
|
+
<i class="i-ic-round-search text-icon" />
|
|
266
|
+
</template>
|
|
267
|
+
{{ submitButtonText }}
|
|
268
|
+
</NButton>
|
|
269
|
+
<NButton v-if="showResetButton" size="small" secondary v-bind="resetBtnProps" @click="resetFields">
|
|
270
|
+
<template #icon>
|
|
271
|
+
<i class="i-ic-round-refresh text-icon" />
|
|
272
|
+
</template>
|
|
273
|
+
{{ resetButtonText }}
|
|
274
|
+
</NButton>
|
|
275
|
+
</template>
|
|
276
|
+
</QuiForm>
|
|
277
|
+
</NCollapseItem>
|
|
278
|
+
</NCollapse>
|
|
279
|
+
</NCard>
|
|
280
|
+
</template>
|
|
281
|
+
|
|
282
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ButtonProps } from 'naive-ui'
|
|
2
|
+
import type { FormProps } from '../form'
|
|
3
|
+
|
|
4
|
+
export interface Props extends FormProps {
|
|
5
|
+
title?: string
|
|
6
|
+
// 是否显示操作按钮(查询/重置)
|
|
7
|
+
showActionButtonGroup?: boolean
|
|
8
|
+
// 显示重置按钮
|
|
9
|
+
showResetButton?: boolean
|
|
10
|
+
// 重置按钮配置
|
|
11
|
+
resetButtonOptions?: ButtonProps
|
|
12
|
+
// 显示确认按钮
|
|
13
|
+
showSubmitButton?: boolean
|
|
14
|
+
// 确认按钮配置
|
|
15
|
+
submitButtonOptions?: ButtonProps
|
|
16
|
+
// 展开收起按钮
|
|
17
|
+
showAdvancedButton?: boolean
|
|
18
|
+
// 受控的展开状态
|
|
19
|
+
expanded?: boolean
|
|
20
|
+
// 确认按钮文字
|
|
21
|
+
submitButtonText?: string
|
|
22
|
+
// 重置按钮文字
|
|
23
|
+
resetButtonText?: string
|
|
24
|
+
// 重置时是否提交表单
|
|
25
|
+
submitOnReset?: boolean
|
|
26
|
+
}
|