@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,174 @@
1
+ <script setup lang="ts">
2
+ import type { MenuInst, MenuOption } from 'naive-ui'
3
+ // 解决 TS2742: The inferred type cannot be named without a reference to ...
4
+ // 显式引用这些类型以确保生成的声明文件是可移植的
5
+ import type {} from 'treemate'
6
+ import type { Slots } from 'vue'
7
+ import type {} from 'vueuc'
8
+ import { computed, ref, unref, useSlots, watch } from 'vue'
9
+ import { useRouter } from 'vue-router'
10
+ import { hasSlotContent } from '../../../share/slot'
11
+ import { SIDE_GROUP_LAYOUT_TYPES, SIDE_MIXED_LAYOUT_TYPES } from '../const'
12
+ import { useContext } from '../context'
13
+
14
+ import { findNodeByKey, renderMenuLabel, resolveMainSubFromActive, updateMenuTree } from '../utils'
15
+ import AppLeftLogoInfo from './AppLeftLogoInfo.vue'
16
+
17
+ const { isCollapsed, collapsedWidth, siderWidth, headerHeight, bordered, inverted, isLeftMain, isTopMain, activeKey, mainActiveKey, subActiveKey, type, menuOptions: options, mainMenuOptions, subMenuOptions, updateActiveKey, updateIsCollapsed, accordion, hasSiderLayout } = useContext()!
18
+
19
+ const menuInstRef = ref<MenuInst | null>(null)
20
+
21
+ const width = computed<number>(() => {
22
+ if (unref(isCollapsed))
23
+ return unref(collapsedWidth)!
24
+
25
+ return unref(siderWidth)!
26
+ })
27
+
28
+ function handleUpdateCollapsed(v: boolean) {
29
+ updateIsCollapsed(v)
30
+ }
31
+
32
+ const isSideMixedMenu = computed<boolean>(() => SIDE_MIXED_LAYOUT_TYPES.includes(unref(type)))
33
+ const isSideGroupMenu = computed<boolean>(() => SIDE_GROUP_LAYOUT_TYPES.includes(unref(type)))
34
+
35
+ const active = ref('')
36
+ const expandedKeys = ref<string[]>([])
37
+
38
+ function handleUpdateExpandedKeys(keys: string[]) {
39
+ expandedKeys.value = keys
40
+ }
41
+
42
+ // 统一处理激活状态和滚动/展开
43
+ watch(() => unref(activeKey), (newKey) => {
44
+ if (!newKey)
45
+ return
46
+
47
+ const { mainKey, subKey } = resolveMainSubFromActive(unref(options) as any[], newKey)
48
+ mainActiveKey.value = mainKey || ''
49
+ subActiveKey.value = subKey || ''
50
+
51
+ if (unref(type) === 'side-menu' || unref(type) === 'side-group-menu' || unref(type) === 'side-mixed-menu') {
52
+ active.value = newKey
53
+ }
54
+ else {
55
+ if (unref(isLeftMain)) {
56
+ active.value = mainKey || ''
57
+ }
58
+
59
+ if (unref(isTopMain)) {
60
+ active.value = subKey || ''
61
+ }
62
+ }
63
+
64
+ // 只有在激活项变化时才调用 showOption,避免干扰手动展开/收起
65
+ if (active.value) {
66
+ menuInstRef.value?.showOption(active.value)
67
+ }
68
+ }, { immediate: true })
69
+
70
+ const router = useRouter()
71
+ function handleUpdateValue(key: string) {
72
+ const node = findNodeByKey(unref(options) as any[], key)
73
+ if (node?.href || /^https?:\/\//.test(key)) {
74
+ return
75
+ }
76
+
77
+ updateActiveKey(key)
78
+ active.value = key
79
+ menuInstRef.value?.showOption(key)
80
+
81
+ if (unref(isLeftMain) && hasSiderLayout.value) {
82
+ const { subKey } = resolveMainSubFromActive(unref(options) as any[], key)
83
+ if (subKey) {
84
+ if (subKey.startsWith('/')) {
85
+ router.push(subKey)
86
+ }
87
+ else {
88
+ router.push({ name: subKey })
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ const menuOptions = computed(() => {
95
+ const transformToGroups = (items: MenuOption[]): MenuOption[] => {
96
+ return updateMenuTree(items as any[], (item) => {
97
+ if (item.children && item.children.length > 0) {
98
+ return { ...item, type: 'group' } as MenuOption
99
+ }
100
+ return item
101
+ }) as MenuOption[]
102
+ }
103
+
104
+ // 1. 获取基础菜单项
105
+ let baseOptions: MenuOption[] = []
106
+ const currentType = unref(type)
107
+ if (currentType === 'side-menu' || currentType === 'side-group-menu' || currentType === 'side-mixed-menu') {
108
+ // 如果是左侧主菜单类布局(非混合顶部),使用完整选项
109
+ baseOptions = unref(options) || []
110
+ }
111
+ else {
112
+ // 否则(如 top-menu/2, top-group-menu/2 等),根据主次关系选择子菜单
113
+ baseOptions = unref(isLeftMain) ? unref(mainMenuOptions) : unref(subMenuOptions)
114
+ }
115
+
116
+ // 2. 根据布局类型进行转换
117
+ if (isSideGroupMenu.value) {
118
+ return transformToGroups(baseOptions)
119
+ }
120
+
121
+ if (isSideMixedMenu.value) {
122
+ // 混合菜单:第一层保持原样(通常是图标),第二层开始转为 group
123
+ return baseOptions.map((item) => {
124
+ if (item.children && item.children.length) {
125
+ return {
126
+ ...item,
127
+ children: transformToGroups(item.children)
128
+ }
129
+ }
130
+ return item
131
+ })
132
+ }
133
+
134
+ return baseOptions
135
+ })
136
+
137
+ const slots: Slots = useSlots()
138
+ const hasDefaultSlot = computed<boolean>(() => hasSlotContent(slots.default))
139
+ </script>
140
+
141
+ <template>
142
+ <n-layout-sider
143
+ class="transition-all duration-300 relative"
144
+ position="absolute"
145
+ collapse-mode="width"
146
+ show-trigger="bar"
147
+ :width="width"
148
+ :collapsed-width="collapsedWidth"
149
+ :bordered="bordered"
150
+ :inverted="inverted"
151
+ :collapsed="isCollapsed"
152
+ :native-scrollbar="false"
153
+ :style="{ top: unref(isTopMain) ? `${unref(headerHeight)}px` : 0 }"
154
+ @update:collapsed="handleUpdateCollapsed"
155
+ >
156
+ <div v-if="isLeftMain">
157
+ <slot v-if="hasDefaultSlot" />
158
+ <AppLeftLogoInfo v-else />
159
+ </div>
160
+
161
+ <n-menu
162
+ ref="menuInstRef"
163
+ :value="active"
164
+ :expanded-keys="expandedKeys"
165
+ :options="menuOptions"
166
+ :render-label="renderMenuLabel"
167
+ :collapsed-width="collapsedWidth"
168
+ :inverted="inverted"
169
+ :accordion="accordion"
170
+ @update:value="handleUpdateValue"
171
+ @update:expanded-keys="handleUpdateExpandedKeys"
172
+ />
173
+ </n-layout-sider>
174
+ </template>
@@ -0,0 +1,366 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 通用布局过渡组件
4
+ *
5
+ * 为侧边栏、头部、底部、内容区域提供统一的进场/出场动画能力,
6
+ * 通过 `mode` 控制动画类型,适配宽度/高度/位移/透明等常见过渡。
7
+ *
8
+ * @param props.mode - 动画模式:'fade' | 'slide-x' | 'slide-y' | 'scale' | 'width' | 'height'
9
+ * @param props.duration - 过渡时长(毫秒),默认 250
10
+ * @param props.easing - 缓动函数,默认 cubic-bezier(0.25,0.46,0.45,0.94)
11
+ * @returns 无返回值(渲染一个 <Transition> 包裹的插槽)
12
+ *
13
+ * @example
14
+ * ```vue
15
+ * <LayoutTransition mode="width">
16
+ * <AppSidebar v-if="showSider" />
17
+ * </LayoutTransition>
18
+ * ```
19
+ *
20
+ * @remarks
21
+ * - width/height 模式使用 scrollWidth/scrollHeight 进行展开动画,before-leave 固定当前尺寸避免抖动
22
+ * - slide-x/slide-y 使用 transform 与 opacity 联合过渡
23
+ * - scale 使用轻微缩放与透明度过渡
24
+ *
25
+ * @security
26
+ * - 仅修改元素内联样式;不涉及外部副作用
27
+ *
28
+ * @performance
29
+ * - 依赖浏览器原生过渡;时间复杂度与节点尺寸无关
30
+ */
31
+ const props = withDefaults(defineProps<{
32
+ mode?: 'fade' | 'slide-x' | 'slide-y' | 'scale' | 'width' | 'height'
33
+ duration?: number
34
+ easing?: string
35
+ appear?: boolean
36
+ }>(), {
37
+ mode: 'fade',
38
+ duration: 250,
39
+ easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
40
+ appear: true
41
+ })
42
+
43
+ /**
44
+ * 设置通用过渡样式
45
+ * 将 transition 应用于节点,按需组合属性。
46
+ */
47
+ function setTransition(node: HTMLElement, propsList: string[]) {
48
+ node.style.transition = propsList.map(p => `${p} ${props.duration}ms ${props.easing}`).join(', ')
49
+ }
50
+
51
+ /**
52
+ * 宽度入场动画(展开)
53
+ *
54
+ * 初始 width=0,随后过渡到 scrollWidth,实现展开效果。
55
+ *
56
+ * @param el - 进入的元素
57
+ */
58
+ function widthEnter(el: Element) {
59
+ const node = el as HTMLElement
60
+ const computedStyle = window.getComputedStyle(node)
61
+ const target
62
+ = Number.parseFloat(computedStyle.width || '0')
63
+ || node.getBoundingClientRect().width
64
+ || node.offsetWidth
65
+ || node.scrollWidth
66
+ if (!target || target <= 0) {
67
+ node.style.transition = ''
68
+ node.style.overflow = ''
69
+ node.style.width = ''
70
+ return
71
+ }
72
+ node.style.overflow = 'hidden'
73
+ node.style.width = '0px'
74
+ setTransition(node, ['width'])
75
+ requestAnimationFrame(() => {
76
+ node.style.width = `${target}px`
77
+ })
78
+ }
79
+
80
+ /**
81
+ * 宽度离场前动画(收起)
82
+ *
83
+ * 固定当前宽度,然后过渡到 0。
84
+ *
85
+ * @param el - 离场的元素
86
+ */
87
+ function widthBeforeLeave(el: Element) {
88
+ const node = el as HTMLElement
89
+ const current = node.offsetWidth
90
+ node.style.overflow = 'hidden'
91
+ node.style.width = `${current}px`
92
+ setTransition(node, ['width'])
93
+ requestAnimationFrame(() => {
94
+ node.style.width = '0px'
95
+ })
96
+ }
97
+
98
+ /**
99
+ * 高度入场动画(展开)
100
+ *
101
+ * 初始 height=0,随后过渡到 scrollHeight,实现展开效果。
102
+ *
103
+ * @param el - 进入的元素
104
+ */
105
+ function heightEnter(el: Element) {
106
+ const node = el as HTMLElement
107
+ const target = node.scrollHeight
108
+ node.style.overflow = 'hidden'
109
+ node.style.height = '0px'
110
+ setTransition(node, ['height'])
111
+ requestAnimationFrame(() => {
112
+ node.style.height = `${target}px`
113
+ })
114
+ }
115
+
116
+ /**
117
+ * 高度离场前动画(收起)
118
+ *
119
+ * 固定当前高度,然后过渡到 0。
120
+ *
121
+ * @param el - 离场的元素
122
+ */
123
+ function heightBeforeLeave(el: Element) {
124
+ const node = el as HTMLElement
125
+ const current = node.offsetHeight
126
+ node.style.overflow = 'hidden'
127
+ node.style.height = `${current}px`
128
+ setTransition(node, ['height'])
129
+ requestAnimationFrame(() => {
130
+ node.style.height = '0px'
131
+ })
132
+ }
133
+
134
+ /**
135
+ * 透明入场动画
136
+ *
137
+ * opacity: 0 -> 1
138
+ *
139
+ * @param el - 进入的元素
140
+ */
141
+ function fadeEnter(el: Element) {
142
+ const node = el as HTMLElement
143
+ node.style.opacity = '0'
144
+ setTransition(node, ['opacity'])
145
+ requestAnimationFrame(() => {
146
+ node.style.opacity = '1'
147
+ })
148
+ }
149
+
150
+ /**
151
+ * 透明离场前动画
152
+ *
153
+ * opacity: 1 -> 0
154
+ *
155
+ * @param el - 离场的元素
156
+ */
157
+ function fadeBeforeLeave(el: Element) {
158
+ const node = el as HTMLElement
159
+ node.style.opacity = '1'
160
+ setTransition(node, ['opacity'])
161
+ requestAnimationFrame(() => {
162
+ node.style.opacity = '0'
163
+ })
164
+ }
165
+
166
+ /**
167
+ * 横向位移入场动画
168
+ *
169
+ * transform: translateX(-8px) -> 0, opacity: 0 -> 1
170
+ *
171
+ * @param el - 进入的元素
172
+ */
173
+ function slideXEnter(el: Element) {
174
+ const node = el as HTMLElement
175
+ node.style.opacity = '0'
176
+ node.style.transform = 'translateX(-8px)'
177
+ setTransition(node, ['transform', 'opacity'])
178
+ requestAnimationFrame(() => {
179
+ node.style.opacity = '1'
180
+ node.style.transform = 'translateX(0)'
181
+ })
182
+ }
183
+
184
+ /**
185
+ * 横向位移离场前动画
186
+ *
187
+ * transform: 0 -> translateX(-8px), opacity: 1 -> 0
188
+ *
189
+ * @param el - 离场的元素
190
+ */
191
+ function slideXBeforeLeave(el: Element) {
192
+ const node = el as HTMLElement
193
+ node.style.opacity = '1'
194
+ node.style.transform = 'translateX(0)'
195
+ setTransition(node, ['transform', 'opacity'])
196
+ requestAnimationFrame(() => {
197
+ node.style.opacity = '0'
198
+ node.style.transform = 'translateX(-8px)'
199
+ })
200
+ }
201
+
202
+ /**
203
+ * 纵向位移入场动画
204
+ *
205
+ * transform: translateY(-6px) -> 0, opacity: 0 -> 1
206
+ *
207
+ * @param el - 进入的元素
208
+ */
209
+ function slideYEnter(el: Element) {
210
+ const node = el as HTMLElement
211
+ node.style.opacity = '0'
212
+ node.style.transform = 'translateY(-6px)'
213
+ setTransition(node, ['transform', 'opacity'])
214
+ requestAnimationFrame(() => {
215
+ node.style.opacity = '1'
216
+ node.style.transform = 'translateY(0)'
217
+ })
218
+ }
219
+
220
+ /**
221
+ * 纵向位移离场前动画
222
+ *
223
+ * transform: 0 -> translateY(-6px), opacity: 1 -> 0
224
+ *
225
+ * @param el - 离场的元素
226
+ */
227
+ function slideYBeforeLeave(el: Element) {
228
+ const node = el as HTMLElement
229
+ node.style.opacity = '1'
230
+ node.style.transform = 'translateY(0)'
231
+ setTransition(node, ['transform', 'opacity'])
232
+ requestAnimationFrame(() => {
233
+ node.style.opacity = '0'
234
+ node.style.transform = 'translateY(-6px)'
235
+ })
236
+ }
237
+
238
+ /**
239
+ * 轻微缩放入场动画
240
+ *
241
+ * scale: 0.98 -> 1, opacity: 0 -> 1
242
+ *
243
+ * @param el - 进入的元素
244
+ */
245
+ function scaleEnter(el: Element) {
246
+ const node = el as HTMLElement
247
+ node.style.opacity = '0'
248
+ node.style.transform = 'scale(0.98)'
249
+ setTransition(node, ['transform', 'opacity'])
250
+ requestAnimationFrame(() => {
251
+ node.style.opacity = '1'
252
+ node.style.transform = 'scale(1)'
253
+ })
254
+ }
255
+
256
+ /**
257
+ * 轻微缩放离场前动画
258
+ *
259
+ * scale: 1 -> 0.98, opacity: 1 -> 0
260
+ *
261
+ * @param el - 离场的元素
262
+ */
263
+ function scaleBeforeLeave(el: Element) {
264
+ const node = el as HTMLElement
265
+ node.style.opacity = '1'
266
+ node.style.transform = 'scale(1)'
267
+ setTransition(node, ['transform', 'opacity'])
268
+ requestAnimationFrame(() => {
269
+ node.style.opacity = '0'
270
+ node.style.transform = 'scale(0.98)'
271
+ })
272
+ }
273
+
274
+ /**
275
+ * 清理动画样式
276
+ *
277
+ * 移除过渡、溢出、尺寸、透明度、位移等,避免影响后续布局。
278
+ *
279
+ * @param el - 离场后的元素
280
+ */
281
+ function afterLeave(el: Element) {
282
+ const node = el as HTMLElement
283
+ node.style.transition = ''
284
+ node.style.overflow = ''
285
+ node.style.width = ''
286
+ node.style.height = ''
287
+ node.style.opacity = ''
288
+ node.style.transform = ''
289
+ }
290
+
291
+ /**
292
+ * 统一进场处理
293
+ *
294
+ * 根据 `mode` 分派具体动画。
295
+ *
296
+ * @param el - 进入元素
297
+ */
298
+ function handleEnter(el: Element) {
299
+ switch (props.mode) {
300
+ case 'width':
301
+ widthEnter(el)
302
+ break
303
+ case 'height':
304
+ heightEnter(el)
305
+ break
306
+ case 'slide-x':
307
+ slideXEnter(el)
308
+ break
309
+ case 'slide-y':
310
+ slideYEnter(el)
311
+ break
312
+ case 'scale':
313
+ scaleEnter(el)
314
+ break
315
+ default:
316
+ fadeEnter(el)
317
+ }
318
+ }
319
+
320
+ /**
321
+ * 统一离场前处理
322
+ *
323
+ * 根据 `mode` 分派具体动画。
324
+ *
325
+ * @param el - 离场元素
326
+ */
327
+ function handleBeforeLeave(el: Element) {
328
+ switch (props.mode) {
329
+ case 'width':
330
+ widthBeforeLeave(el)
331
+ break
332
+ case 'height':
333
+ heightBeforeLeave(el)
334
+ break
335
+ case 'slide-x':
336
+ slideXBeforeLeave(el)
337
+ break
338
+ case 'slide-y':
339
+ slideYBeforeLeave(el)
340
+ break
341
+ case 'scale':
342
+ scaleBeforeLeave(el)
343
+ break
344
+ default:
345
+ fadeBeforeLeave(el)
346
+ }
347
+ }
348
+ </script>
349
+
350
+ <template>
351
+ <Transition
352
+ :appear="props.appear"
353
+ @enter="handleEnter"
354
+ @before-leave="handleBeforeLeave"
355
+ @after-leave="afterLeave"
356
+ >
357
+ <slot />
358
+ </Transition>
359
+ <!-- 用法:
360
+ <LayoutTransition mode="width"><AppSidebar v-if="hasSider"/></LayoutTransition>
361
+ <LayoutTransition mode="height"><AppHeader v-if="showHeader"/></LayoutTransition>
362
+ <LayoutTransition mode="fade"><AppMain v-if="ready"/></LayoutTransition>
363
+ -->
364
+ </template>
365
+
366
+ <style scoped></style>
File without changes
@@ -0,0 +1,36 @@
1
+ import type { MenuOption } from 'naive-ui'
2
+ import type { RouteRecordRaw } from 'vue-router'
3
+ import type { LayoutType } from './types'
4
+
5
+ export type Props = Partial<{
6
+ /** @description 布局类型 */
7
+ type: LayoutType
8
+ /** @description 是否显示边框 */
9
+ bordered: boolean
10
+ /** @description 反色样式(Header/Sider/Footer/Menu) */
11
+ inverted: boolean
12
+ /** @description 是否折叠侧边栏 */
13
+ isCollapsed: boolean
14
+ /** @description 是否显示底部 */
15
+ showFooter: boolean
16
+ /** @description 头部高度 */
17
+ headerHeight: number
18
+ /** @description 底部高度 */
19
+ footerHeight: number
20
+ /** @description 底部是否占满宽度 */
21
+ footerFull: boolean
22
+ /** @description 侧边栏宽度 */
23
+ siderWidth: number
24
+ /** @description 侧边栏混合模式的宽度 */
25
+ siderMixedWidth: number
26
+ /** @description 折叠侧边栏宽度 */
27
+ collapsedWidth: number
28
+ /** @description 当前激活的路由键 */
29
+ activeKey: string
30
+ /** @description 菜单路由 */
31
+ menuOptions: MenuOption[]
32
+ /** @description 基础路由 */
33
+ baseRoutes: RouteRecordRaw[]
34
+ /** @description 是否开启手风琴模式 */
35
+ accordion: boolean
36
+ }>
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 应用布局类型枚举(格式:[主要]-[侧边栏模式]-[菜单选项])
3
+ * 用于动态切换页面整体结构,省略 "layout" 后缀以保持简洁
4
+ */
5
+ export type LayoutType
6
+ = | 'side-menu'
7
+ | 'side-menu/2'
8
+ | 'side-mixed-menu'
9
+ | 'side-group-menu'
10
+ | 'top-menu'
11
+ | 'top-menu/2'
12
+ | 'top-group-menu/2'
13
+ | 'top-mixed-menu/2'
14
+ | 'blank'
15
+
16
+ export interface RouteMeta {
17
+ /**
18
+ * 页面标题(用于 document.title 或 tab 标签)
19
+ */
20
+ title?: string
21
+
22
+ /**
23
+ * 是否需要认证(true: 需登录,false: 免登录)
24
+ * @default true
25
+ */
26
+ requiresAuth?: boolean
27
+
28
+ /**
29
+ * 所需权限(字符串数组或字符串)
30
+ */
31
+ permissions?: string | string[]
32
+
33
+ /**
34
+ * 所属角色(如 'admin', 'user')
35
+ */
36
+ roles?: string[]
37
+
38
+ /**
39
+ * 是否在侧边栏菜单中显示
40
+ * @default false
41
+ */
42
+ hideMenu?: boolean
43
+
44
+ /**
45
+ * 侧边栏菜单选中
46
+ * @default string
47
+ */
48
+ activeMenu?: import('vue-router').RouteRecordRedirectOption
49
+
50
+ /**
51
+ * 菜单图标(Iconify 格式,如 'mdi:home')
52
+ */
53
+ icon?: string
54
+
55
+ /**
56
+ * 使用的布局名称(如 'blank', 'main')
57
+ */
58
+ layout?: LayoutType
59
+
60
+ /**
61
+ * 是否开启 keep-alive 缓存
62
+ */
63
+ keepAlive?: boolean
64
+
65
+ /**
66
+ * 页面过渡动画名
67
+ */
68
+ transition?: string
69
+
70
+ /**
71
+ * 排序
72
+ */
73
+ order?: number
74
+
75
+ /**
76
+ * 外链
77
+ */
78
+ href?: string
79
+ }