@peng_kai/kit 0.0.6 → 0.0.8

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.
@@ -0,0 +1,30 @@
1
+ import type { RouteLocationNormalizedLoaded, Router } from 'vue-router'
2
+
3
+ export function getTitle(route?: Pick<RouteLocationNormalizedLoaded, 'meta'>) {
4
+ const mTitle = route?.meta?.title
5
+
6
+ return typeof mTitle === 'function' ? mTitle() : mTitle
7
+ }
8
+
9
+ export function getMenusByRouter(router: Router) {
10
+ const menuOrderRE = /^(?<key>\w*)@(?<order>\d+)$/
11
+ const routes = router.getRoutes()
12
+
13
+ return routes
14
+ .filter(route => menuOrderRE.test((route.meta.menuOrder ?? '')))
15
+ .map((route) => {
16
+ const res = route.meta!.menuOrder!.match(menuOrderRE)
17
+ const parentKey = res?.groups?.key
18
+ const order = Number(res?.groups?.order)
19
+ const name = route.name as string
20
+ const menu = {
21
+ key: name,
22
+ label: route.meta.title ?? name,
23
+ icon: route.meta.icon,
24
+ trigger: parentKey ? () => router.push({ name }) : () => {},
25
+ order,
26
+ }
27
+
28
+ return { parentKey, menu }
29
+ })
30
+ }
@@ -1,4 +1,5 @@
1
1
  export { definePage } from './definePage'
2
2
  export { defineRoute, getRoutes } from './defineRoute'
3
+ export { getMenusByRouter, getTitle as getRouteTitle } from './defineRoute.helpers'
3
4
  export { defineRouteGuard, getRouteGuards } from './defineRouteGuard'
4
5
  export { defineStartup, getStartups } from './defineStartup'
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { Button as AButton } from "ant-design-vue";
3
+
4
+ const props = defineProps<{
5
+ loading?: boolean
6
+ }>()
7
+ const emits = defineEmits<{
8
+ (e: 'filter'): void
9
+ (e: 'reset'): void
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div class="flex w-min">
15
+ <AButton class="mr-2 filter-btn" type="primary" :loading="props.loading" @click="emits('filter')">
16
+ 查询
17
+ </AButton>
18
+ <AButton :disabled="props.loading" @click="emits('reset')">
19
+ 重置
20
+ </AButton>
21
+ </div>
22
+ </template>
23
+
24
+ <style scoped lang="scss">
25
+ .filter-btn {
26
+ position: relative;
27
+
28
+ :deep(.ant-btn-loading-icon) {
29
+ position: absolute;
30
+ left: 50%;
31
+ transform: translateX(-50%);
32
+ transition: none !important;
33
+
34
+ .anticon-loading {
35
+ margin-inline-end: 0 !important;
36
+ }
37
+
38
+ + span {
39
+ visibility: hidden;
40
+ }
41
+ }
42
+ }
43
+ </style>
@@ -0,0 +1,6 @@
1
+ export { usePage } from './usePage'
2
+ export type { TPageState, IBreadcrumb } from './usePage'
3
+ export { useMenu } from './useMenu'
4
+ export type { TMenu } from './useMenu'
5
+ export { usePageTab } from './usePageTab'
6
+ export { usePermission } from './usePermission'
@@ -0,0 +1,128 @@
1
+ import { createGlobalState } from '@vueuse/core'
2
+ import { reactive, ref, computed } from 'vue'
3
+ import type { UnwrapNestedRefs, VNode, Ref } from 'vue'
4
+
5
+ export { useMenu }
6
+ export type { TMenu }
7
+
8
+ interface IMenuConfig {
9
+ key: string
10
+ label: string | (() => string)
11
+ icon?: () => VNode
12
+ order: number
13
+ trigger: () => void
14
+ children?: IMenuConfig[]
15
+ }
16
+
17
+ interface IMenuReactive {
18
+ key: string
19
+ label: Ref<string>
20
+ icon: Ref<VNode | null>
21
+ order: number
22
+ trigger: () => void
23
+ children?: IMenuReactive[]
24
+ }
25
+
26
+ type TMenu = UnwrapNestedRefs<IMenuReactive>
27
+
28
+ const useMenu = createGlobalState(() => {
29
+ const menus = reactive<TMenu[]>([])
30
+ const collapsed = ref(false)
31
+
32
+ const findMenu = (menus: TMenu[], key: string) => {
33
+ const queue = [...menus]
34
+
35
+ while (queue.length > 0) {
36
+ const menu = queue.shift()!
37
+
38
+ if (menu.key === key)
39
+ return menu
40
+
41
+ if (menu.children)
42
+ queue.push(...menu.children)
43
+ }
44
+
45
+ return undefined
46
+ }
47
+
48
+ /**
49
+ * 获取目标路由的路径
50
+ * @param key 目标路由
51
+ */
52
+ const getMenuPath = (key: string) => {
53
+ const path: TMenu[] = []
54
+
55
+ const _getMenuPath = (menus: TMenu[], key: string) => {
56
+ for (const menu of menus) {
57
+ if (menu.key === key) {
58
+ path.push(menu)
59
+ return true
60
+ }
61
+
62
+ if (menu.children) {
63
+ path.push(menu)
64
+ if (_getMenuPath(menu.children, key))
65
+ return true
66
+ path.pop()
67
+ }
68
+ }
69
+
70
+ return false
71
+ }
72
+
73
+ _getMenuPath(menus, key)
74
+
75
+ return path
76
+ }
77
+
78
+ const addMenu = (menuConfig: IMenuConfig, parentKey?: string) => {
79
+ const labelGetter = typeof menuConfig.label === 'function' ? menuConfig.label : (() => menuConfig.label) as (() => string)
80
+ const iconGetter = typeof menuConfig.icon === 'function' ? menuConfig.icon : () => null
81
+
82
+ const _menu = reactive<IMenuReactive>({
83
+ key: menuConfig.key,
84
+ label: computed(labelGetter),
85
+ icon: computed(iconGetter),
86
+ trigger: menuConfig.trigger,
87
+ order: menuConfig.order,
88
+ })
89
+
90
+ if (parentKey) {
91
+ const parentMenu = findMenu(menus, parentKey)
92
+
93
+ if (!parentMenu)
94
+ return
95
+
96
+ const children = reactive(parentMenu.children ?? [])
97
+ children.push(_menu)
98
+ children.sort((a, b) => a.order - b.order)
99
+ parentMenu.children = children
100
+ }
101
+ else {
102
+ menus.push(_menu)
103
+ menus.sort((a, b) => a.order - b.order)
104
+ }
105
+ }
106
+
107
+ const removeMenu = (key: string) => {
108
+ const _remove = (menus: TMenu[], key: string) => {
109
+ for (let i = 0; i < menus.length; i++) {
110
+ const menu = menus[i]
111
+ if (menu.key === key) {
112
+ menus.splice(i, 1)
113
+ return
114
+ }
115
+ if (menu.children)
116
+ _remove(menu.children, key)
117
+ }
118
+ }
119
+
120
+ return _remove(menus, key)
121
+ }
122
+
123
+ const getMenu = (key: string) => {
124
+ return findMenu(menus, key)
125
+ }
126
+
127
+ return { menus, collapsed, getMenuPath, addMenu, removeMenu, getMenu }
128
+ })
@@ -0,0 +1,138 @@
1
+ import { createGlobalState, usePrevious } from '@vueuse/core'
2
+ import type { RouteLocationNormalizedLoaded } from 'vue-router'
3
+ import { shallowRef, ref, reactive, computed, watch, readonly } from 'vue'
4
+ import type { VNode } from 'vue'
5
+ import { useMenu } from './useMenu'
6
+ import type { TMenu } from './useMenu'
7
+ import { getTitle } from '../defines/defineRoute.helpers'
8
+ import { kitDependencies } from "../../kitDependencies";
9
+
10
+ export { usePage }
11
+ export type { TPageState, IBreadcrumb }
12
+
13
+ type TPageState = ReturnType<typeof getPageState>
14
+
15
+ interface IBreadcrumb {
16
+ title: string
17
+ icon?: VNode | null
18
+ trigger?: () => void
19
+ children?: IBreadcrumb[]
20
+ }
21
+
22
+ const usePage = createGlobalState(() => {
23
+ const router = kitDependencies.useRouter()
24
+ const currentPageNode = shallowRef<VNode>()
25
+ const currentPageKey = ref('')
26
+ const pageCacheList = reactive<string[]>([])
27
+ const pageStateMap = new Map<string, TPageState>()
28
+ const currentPageState = computed(() => pageStateMap.get(currentPageKey.value))
29
+ const previousPageState = usePrevious(currentPageState)
30
+
31
+ const openPage = (key: string) => {
32
+ const route = pageStateMap.get(key)?.route
33
+
34
+ if (!route)
35
+ return
36
+
37
+ router.push(route.fullPath)
38
+ }
39
+
40
+ const closePage = (key: string) => {
41
+ if (!pageStateMap.has(key))
42
+ return
43
+
44
+ const cacheIndex = pageCacheList.indexOf(key)
45
+ cacheIndex >= 0 && pageCacheList.splice(cacheIndex, 1)
46
+ // 关闭当前页面则返回上一个路由
47
+ currentPageKey.value === key && router.go(-1)
48
+ pageStateMap.delete(key)
49
+ }
50
+
51
+ const setPage = (pageNode: VNode, route: RouteLocationNormalizedLoaded) => {
52
+ const pageKey = route?.meta?.pageKeyFn?.(route) ?? route.fullPath
53
+ const pageInfo = getPageState(route)
54
+ const canKeepAlive = route.meta.keepAlive && !pageCacheList.includes(pageKey)
55
+
56
+ canKeepAlive && pageCacheList.push(pageKey)
57
+ pageStateMap.has(pageKey) || pageStateMap.set(pageKey, pageInfo)
58
+
59
+ ;(pageNode as any).type.name = pageKey
60
+ currentPageKey.value = pageKey
61
+ currentPageNode.value = pageNode
62
+
63
+ return pageNode
64
+ }
65
+
66
+ const getCurrentPageState = () => {
67
+ return pageStateMap.get(currentPageKey.value)
68
+ }
69
+
70
+ // 监听 title 变化
71
+ watch(() => currentPageState.value?.title, (title) => {
72
+ let _title = title
73
+
74
+ if (!_title) {
75
+ const pageState = currentPageState.value
76
+
77
+ // 当前 title 为空时,使用路由 title
78
+ if (pageState) {
79
+ _title = getTitle(pageState?.route)
80
+ pageState.title = _title!
81
+ }
82
+ }
83
+
84
+ document.title = _title!
85
+ })
86
+
87
+ return {
88
+ currentPageNode,
89
+ pageCacheList,
90
+ pageStateMap,
91
+ currentPageKey,
92
+ currentPageState,
93
+ getCurrentPageState,
94
+ previousPageState,
95
+ setPage,
96
+ openPage,
97
+ closePage,
98
+ }
99
+ })
100
+
101
+ function getPageState(route: RouteLocationNormalizedLoaded) {
102
+ const { getMenuPath } = useMenu()
103
+ const menuPath = getMenuPath(route.name as string)
104
+ const currentMenu = menuPath.pop()
105
+ const { icon: mIcon } = route.meta
106
+ const title = currentMenu?.label ?? getTitle(route) ?? import.meta.env.VITE_APP_TITLE
107
+ const icon = currentMenu?.icon ?? mIcon?.()
108
+ const state = reactive({
109
+ title,
110
+ icon,
111
+ breadcrumbs: menuPath.map(menuToBreadcrumb),
112
+ route: readonly(route),
113
+ refresh() {
114
+ const menuKey = this.route.name as string
115
+ const { getMenu, getMenuPath } = useMenu()
116
+ const menu = getMenu(menuKey)
117
+ const menuPath = getMenuPath(menuKey)
118
+
119
+ menuPath.pop()
120
+
121
+ this.title = menu?.label ?? getTitle(this.route) ?? import.meta.env.VITE_APP_TITLE
122
+ this.breadcrumbs = menuPath.map(menuToBreadcrumb)
123
+ },
124
+ })
125
+
126
+ return state
127
+ }
128
+
129
+ function menuToBreadcrumb(menu: TMenu) {
130
+ const ret: IBreadcrumb = reactive({
131
+ title: menu.label,
132
+ icon: menu.icon,
133
+ trigger: menu.trigger,
134
+ children: menu.children?.map(menuToBreadcrumb),
135
+ })
136
+
137
+ return ret
138
+ }
@@ -0,0 +1,35 @@
1
+ import { createGlobalState } from '@vueuse/core'
2
+ import { ref, computed, watch } from 'vue'
3
+ import { usePage } from './usePage'
4
+
5
+ export { usePageTab }
6
+
7
+ const usePageTab = createGlobalState(() => {
8
+ const { currentPageKey, pageStateMap, openPage, closePage } = usePage()
9
+ const tabKeyList = ref<string[]>([])
10
+ const tabList = computed(() => tabKeyList.value.map((key) => {
11
+ const state = pageStateMap.get(key)
12
+
13
+ return state ? { key, title: state.title, icon: state.icon } : undefined!
14
+ }).filter(tab => !!tab))
15
+
16
+ const closeTab = (key: string) => {
17
+ const i = tabKeyList.value.indexOf(key)
18
+
19
+ if (i > -1) {
20
+ closePage(key)
21
+ setTimeout(() => tabKeyList.value.splice(i, 1))
22
+ }
23
+ }
24
+
25
+ watch(
26
+ currentPageKey,
27
+ (key) => {
28
+ if (!tabKeyList.value.includes(key))
29
+ tabKeyList.value.push(key)
30
+ },
31
+ { immediate: true },
32
+ )
33
+
34
+ return { activeTab: currentPageKey, tabList, closeTab, openTab: openPage }
35
+ })
@@ -0,0 +1,5 @@
1
+ import { createGlobalState } from '@vueuse/core'
2
+
3
+ export { usePermission }
4
+
5
+ const usePermission = createGlobalState(() => {})
@@ -1,5 +1,8 @@
1
1
  import dayjs from "dayjs";
2
+ import type { useRoute, useRouter } from 'vue-router'
2
3
 
3
4
  export const kitDependencies = {
4
- dayjs: dayjs
5
+ dayjs: dayjs,
6
+ useRoute: (() => {}) as typeof useRoute,
7
+ useRouter: (() => {}) as typeof useRouter,
5
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peng_kai/kit",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,24 +0,0 @@
1
- <script setup lang="ts">
2
- import { Button as AButton } from "ant-design-vue";
3
-
4
- const props = defineProps<{
5
- loading?: boolean
6
- }>()
7
- const emits = defineEmits<{
8
- (e: 'filter'): void
9
- (e: 'reset'): void
10
- }>()
11
- </script>
12
-
13
- <template>
14
- <div class="flex w-min">
15
- <AButton class="mr-2" type="primary" :loading="props.loading" @click="emits('filter')">
16
- 查询
17
- </AButton>
18
- <AButton :disabled="props.loading" @click="emits('reset')">
19
- 重置
20
- </AButton>
21
- </div>
22
- </template>
23
-
24
- <style scoped lang="scss"></style>
File without changes
File without changes
File without changes
File without changes