@peng_kai/kit 0.1.4 → 0.1.6
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/admin/components/filter/src/FilterDrawer.vue +153 -153
- package/admin/components/filter/src/FilterParam.vue +76 -76
- package/admin/components/filter/src/FilterReset.vue +2 -2
- package/admin/components/filter/src/useFilterParams.ts +9 -0
- package/admin/components/scroll-nav/index.ts +1 -1
- package/admin/components/scroll-nav/src/ScrollNav.vue +59 -59
- package/admin/components/text/index.ts +13 -13
- package/admin/components/text/src/Amount.vue +117 -117
- package/admin/components/text/src/Datetime.vue +48 -48
- package/admin/components/text/src/Duration.vue +26 -26
- package/admin/components/text/src/Hash.vue +51 -51
- package/admin/components/text/src/createTagGetter.ts +13 -13
- package/admin/hooks/useMenu.ts +128 -128
- package/admin/hooks/usePage.ts +141 -141
- package/admin/hooks/usePageTab.ts +35 -35
- package/admin/layout/large/Breadcrumb.vue +69 -69
- package/admin/layout/large/Content.vue +24 -24
- package/admin/layout/large/Menu.vue +69 -69
- package/admin/layout/large/PageTab.vue +71 -71
- package/admin/permission/index.ts +4 -4
- package/admin/permission/routerGuard.ts +43 -43
- package/admin/permission/usePermission.ts +52 -52
- package/admin/permission/vuePlugin.ts +30 -30
- package/admin/route-guards/index.ts +3 -3
- package/admin/route-guards/pageProgress.ts +27 -27
- package/admin/route-guards/pageTitle.ts +19 -19
- package/admin/styles/classCover.scss +59 -2
- package/admin/styles/globalCover.scss +54 -54
- package/admin/types/assist.ts +2 -2
- package/antd/components/InputNumberRange.vue +53 -53
- package/antd/directives/formLabelAlign.ts +36 -36
- package/antd/hooks/useAntdDrawer.ts +73 -73
- package/antd/hooks/useAntdTable.ts +82 -71
- package/package.json +55 -54
- package/request/helpers.ts +49 -49
- package/request/interceptors/toLogin.ts +15 -15
- package/request/type.d.ts +92 -92
- package/stylelint.config.cjs +7 -7
- package/tsconfig.json +50 -50
- package/utils/date.ts +44 -0
- package/utils/index.ts +14 -8
- package/utils/number.ts +49 -0
- package/vue/components/infinite-query/index.ts +1 -1
- package/vue/components/infinite-query/src/InfiniteQuery.vue +205 -205
- package/vue/components/infinite-query/src/useCreateTrigger.ts +39 -39
- package/vue/hooks/useComponentRef.ts +7 -1
- package/vue/hooks/useIsDark.ts +5 -0
- package/vue/hooks/useIsMounted.ts +3 -0
- package/vue/hooks/useTeleportTarget.ts +35 -2
- package/vue/index.ts +1 -1
- package/pnpm-lock.yaml +0 -5242
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { computed, ref, unref } from 'vue';
|
|
3
|
-
import type { UseInfiniteQueryReturnType } from '@tanstack/vue-query';
|
|
4
|
-
import { useCreateTrigger } from './useCreateTrigger';
|
|
5
|
-
|
|
6
|
-
export type { ISlotProvide };
|
|
7
|
-
|
|
8
|
-
interface ISlotProvide {
|
|
9
|
-
isInitialLoading: boolean
|
|
10
|
-
isInitialLoadingError: boolean
|
|
11
|
-
isEmpty: boolean
|
|
12
|
-
isMoreLoading: boolean
|
|
13
|
-
isMoreLoadingError: boolean
|
|
14
|
-
isNoMore: boolean
|
|
15
|
-
refetch: () => void
|
|
16
|
-
}
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<script setup lang="ts" generic="T extends UseInfiniteQueryReturnType<any, any>">
|
|
20
|
-
type TPage = T extends UseInfiniteQueryReturnType<infer P, any> ? NonNullable<P> : any;
|
|
21
|
-
type TRecord = TPage extends { list: Array<infer I> } ? I : any;
|
|
22
|
-
|
|
23
|
-
const props = withDefaults(defineProps<{
|
|
24
|
-
queryier: T
|
|
25
|
-
distance?: string
|
|
26
|
-
}>(), {
|
|
27
|
-
distance: '50px',
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const { queryier } = props;
|
|
31
|
-
const pages = computed(() => queryier.data.value?.pages);
|
|
32
|
-
// TODO: 状态之间的互斥仍有问题
|
|
33
|
-
const isInitialLoading = computed(() => queryier.isInitialLoading.value);
|
|
34
|
-
const isInitialLoadingError = computed(() => queryier.isLoadingError.value && !isInitialLoading.value);
|
|
35
|
-
const isMoreLoading = computed(
|
|
36
|
-
() => (queryier.isFetchingNextPage.value || queryier.isFetching.value) && !isInitialLoading.value,
|
|
37
|
-
);
|
|
38
|
-
const isEmpty = computed(() => queryier.isSuccess.value && !pages.value?.[0].list?.length);
|
|
39
|
-
const isMoreLoadingError = computed(() => queryier.isRefetchError.value && !isMoreLoading.value);
|
|
40
|
-
const isNoMore = computed(
|
|
41
|
-
() =>
|
|
42
|
-
!queryier.hasNextPage?.value
|
|
43
|
-
&& !isInitialLoading.value
|
|
44
|
-
&& !isInitialLoadingError.value
|
|
45
|
-
&& !isMoreLoading.value
|
|
46
|
-
&& !isMoreLoadingError.value,
|
|
47
|
-
);
|
|
48
|
-
const $container = ref<HTMLElement>();
|
|
49
|
-
const containerCssVars = computed(() => {
|
|
50
|
-
const ctnEle = unref($container);
|
|
51
|
-
|
|
52
|
-
if (!ctnEle)
|
|
53
|
-
return {};
|
|
54
|
-
|
|
55
|
-
const rect = ctnEle.getBoundingClientRect();
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
'--ctn-height': `${rect.height}px`,
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
function refetchLastPage() {
|
|
63
|
-
queryier.refetch({
|
|
64
|
-
refetchPage(lastPage: any, _index, allPages: any[]) {
|
|
65
|
-
const lastIndex = allPages?.length - 1;
|
|
66
|
-
return lastPage?.pagination?.page === allPages?.[lastIndex]?.pagination?.page;
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function fetchNextPage() {
|
|
72
|
-
const { isFetching, isLoading, hasNextPage } = queryier;
|
|
73
|
-
|
|
74
|
-
if (isFetching.value || isLoading.value || !hasNextPage?.value)
|
|
75
|
-
return;
|
|
76
|
-
|
|
77
|
-
queryier.fetchNextPage();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function refetch() {
|
|
81
|
-
if (isNoMore.value)
|
|
82
|
-
refetchLastPage();
|
|
83
|
-
else if (isMoreLoadingError.value)
|
|
84
|
-
fetchNextPage();
|
|
85
|
-
else
|
|
86
|
-
queryier.refetch();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function triggerFetchNextPage() {
|
|
90
|
-
if (queryier.isError.value)
|
|
91
|
-
return;
|
|
92
|
-
|
|
93
|
-
fetchNextPage();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
useCreateTrigger($container, triggerFetchNextPage, props.distance);
|
|
97
|
-
|
|
98
|
-
const slotProvide = computed<ISlotProvide>(() => ({
|
|
99
|
-
isInitialLoading: isInitialLoading.value,
|
|
100
|
-
isInitialLoadingError: isInitialLoadingError.value,
|
|
101
|
-
isMoreLoading: isMoreLoading.value,
|
|
102
|
-
isMoreLoadingError: isMoreLoadingError.value,
|
|
103
|
-
isNoMore: isNoMore.value,
|
|
104
|
-
isEmpty: isEmpty.value,
|
|
105
|
-
refetch,
|
|
106
|
-
}));
|
|
107
|
-
</script>
|
|
108
|
-
|
|
109
|
-
<template>
|
|
110
|
-
<div ref="$container" class="infinite-query-wrapper" :style="containerCssVars">
|
|
111
|
-
<div class="list">
|
|
112
|
-
<template v-for="(page) of pages">
|
|
113
|
-
<template v-for="record of page.list">
|
|
114
|
-
<!-- eslint-disable-next-line vue/no-extra-parens -->
|
|
115
|
-
<slot name="default" :record="(record as TRecord)" />
|
|
116
|
-
</template>
|
|
117
|
-
</template>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<slot
|
|
121
|
-
name="status" v-bind="slotProvide"
|
|
122
|
-
>
|
|
123
|
-
<slot name="initialLoading">
|
|
124
|
-
<div v-if="isInitialLoading" class="initial-loading">
|
|
125
|
-
<i class="i-svg-spinners:180-ring-with-bg loading-icon" />
|
|
126
|
-
</div>
|
|
127
|
-
</slot>
|
|
128
|
-
|
|
129
|
-
<slot name="initialLoadingError">
|
|
130
|
-
<div v-if="isInitialLoadingError" class="initial-loading-error" @click="refetch()">
|
|
131
|
-
<span>加载失败,点击重试</span>
|
|
132
|
-
</div>
|
|
133
|
-
</slot>
|
|
134
|
-
|
|
135
|
-
<slot name="empty">
|
|
136
|
-
<div v-if="isEmpty" class="empty">
|
|
137
|
-
<span>暂无数据</span>
|
|
138
|
-
</div>
|
|
139
|
-
</slot>
|
|
140
|
-
|
|
141
|
-
<slot name="oreLoading">
|
|
142
|
-
<div v-if="isMoreLoading" class="more-loading">
|
|
143
|
-
<i class="i-svg-spinners:180-ring-with-bg loading-icon" />
|
|
144
|
-
<span class="ml-1 text">加载中...</span>
|
|
145
|
-
</div>
|
|
146
|
-
</slot>
|
|
147
|
-
|
|
148
|
-
<slot name="moreLoadingError">
|
|
149
|
-
<div v-if="isMoreLoadingError" class="more-loading-error" @click="refetch()">
|
|
150
|
-
<span class="text">加载失败,点击重试</span>
|
|
151
|
-
</div>
|
|
152
|
-
</slot>
|
|
153
|
-
|
|
154
|
-
<slot name="noMore">
|
|
155
|
-
<div v-if="isNoMore" class="no-more" @click="refetch()">
|
|
156
|
-
<span class="text">暂无更多</span>
|
|
157
|
-
</div>
|
|
158
|
-
</slot>
|
|
159
|
-
</slot>
|
|
160
|
-
</div>
|
|
161
|
-
</template>
|
|
162
|
-
|
|
163
|
-
<style scoped lang="scss">
|
|
164
|
-
.infinite-query-wrapper {
|
|
165
|
-
overflow: auto;
|
|
166
|
-
font-size: 14px;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.loading-icon {
|
|
170
|
-
display: block;
|
|
171
|
-
color: var(--antd-colorPrimary);
|
|
172
|
-
font-size: 1.2em;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.initial-loading,
|
|
176
|
-
.initial-loading-error,
|
|
177
|
-
.empty,
|
|
178
|
-
.more-loading,
|
|
179
|
-
.more-loading-error,
|
|
180
|
-
.no-more {
|
|
181
|
-
display: flex;
|
|
182
|
-
align-items: center;
|
|
183
|
-
justify-content: center;
|
|
184
|
-
color: var(--antd-colorTextSecondary);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.initial-loading,
|
|
188
|
-
.initial-loading-error {
|
|
189
|
-
height: var(--ctn-height, 100px);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.initial-loading-error {
|
|
193
|
-
cursor: pointer;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.more-loading,
|
|
197
|
-
.more-loading-error,
|
|
198
|
-
.no-more {
|
|
199
|
-
height: 50px;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.more-loading-error {
|
|
203
|
-
cursor: pointer;
|
|
204
|
-
}
|
|
205
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { computed, ref, unref } from 'vue';
|
|
3
|
+
import type { UseInfiniteQueryReturnType } from '@tanstack/vue-query';
|
|
4
|
+
import { useCreateTrigger } from './useCreateTrigger';
|
|
5
|
+
|
|
6
|
+
export type { ISlotProvide };
|
|
7
|
+
|
|
8
|
+
interface ISlotProvide {
|
|
9
|
+
isInitialLoading: boolean
|
|
10
|
+
isInitialLoadingError: boolean
|
|
11
|
+
isEmpty: boolean
|
|
12
|
+
isMoreLoading: boolean
|
|
13
|
+
isMoreLoadingError: boolean
|
|
14
|
+
isNoMore: boolean
|
|
15
|
+
refetch: () => void
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts" generic="T extends UseInfiniteQueryReturnType<any, any>">
|
|
20
|
+
type TPage = T extends UseInfiniteQueryReturnType<infer P, any> ? NonNullable<P> : any;
|
|
21
|
+
type TRecord = TPage extends { list: Array<infer I> } ? I : any;
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<{
|
|
24
|
+
queryier: T
|
|
25
|
+
distance?: string
|
|
26
|
+
}>(), {
|
|
27
|
+
distance: '50px',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { queryier } = props;
|
|
31
|
+
const pages = computed(() => queryier.data.value?.pages);
|
|
32
|
+
// TODO: 状态之间的互斥仍有问题
|
|
33
|
+
const isInitialLoading = computed(() => queryier.isInitialLoading.value);
|
|
34
|
+
const isInitialLoadingError = computed(() => queryier.isLoadingError.value && !isInitialLoading.value);
|
|
35
|
+
const isMoreLoading = computed(
|
|
36
|
+
() => (queryier.isFetchingNextPage.value || queryier.isFetching.value) && !isInitialLoading.value,
|
|
37
|
+
);
|
|
38
|
+
const isEmpty = computed(() => queryier.isSuccess.value && !pages.value?.[0].list?.length);
|
|
39
|
+
const isMoreLoadingError = computed(() => queryier.isRefetchError.value && !isMoreLoading.value);
|
|
40
|
+
const isNoMore = computed(
|
|
41
|
+
() =>
|
|
42
|
+
!queryier.hasNextPage?.value
|
|
43
|
+
&& !isInitialLoading.value
|
|
44
|
+
&& !isInitialLoadingError.value
|
|
45
|
+
&& !isMoreLoading.value
|
|
46
|
+
&& !isMoreLoadingError.value,
|
|
47
|
+
);
|
|
48
|
+
const $container = ref<HTMLElement>();
|
|
49
|
+
const containerCssVars = computed(() => {
|
|
50
|
+
const ctnEle = unref($container);
|
|
51
|
+
|
|
52
|
+
if (!ctnEle)
|
|
53
|
+
return {};
|
|
54
|
+
|
|
55
|
+
const rect = ctnEle.getBoundingClientRect();
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
'--ctn-height': `${rect.height}px`,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function refetchLastPage() {
|
|
63
|
+
queryier.refetch({
|
|
64
|
+
refetchPage(lastPage: any, _index, allPages: any[]) {
|
|
65
|
+
const lastIndex = allPages?.length - 1;
|
|
66
|
+
return lastPage?.pagination?.page === allPages?.[lastIndex]?.pagination?.page;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fetchNextPage() {
|
|
72
|
+
const { isFetching, isLoading, hasNextPage } = queryier;
|
|
73
|
+
|
|
74
|
+
if (isFetching.value || isLoading.value || !hasNextPage?.value)
|
|
75
|
+
return;
|
|
76
|
+
|
|
77
|
+
queryier.fetchNextPage();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function refetch() {
|
|
81
|
+
if (isNoMore.value)
|
|
82
|
+
refetchLastPage();
|
|
83
|
+
else if (isMoreLoadingError.value)
|
|
84
|
+
fetchNextPage();
|
|
85
|
+
else
|
|
86
|
+
queryier.refetch();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function triggerFetchNextPage() {
|
|
90
|
+
if (queryier.isError.value)
|
|
91
|
+
return;
|
|
92
|
+
|
|
93
|
+
fetchNextPage();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
useCreateTrigger($container, triggerFetchNextPage, props.distance);
|
|
97
|
+
|
|
98
|
+
const slotProvide = computed<ISlotProvide>(() => ({
|
|
99
|
+
isInitialLoading: isInitialLoading.value,
|
|
100
|
+
isInitialLoadingError: isInitialLoadingError.value,
|
|
101
|
+
isMoreLoading: isMoreLoading.value,
|
|
102
|
+
isMoreLoadingError: isMoreLoadingError.value,
|
|
103
|
+
isNoMore: isNoMore.value,
|
|
104
|
+
isEmpty: isEmpty.value,
|
|
105
|
+
refetch,
|
|
106
|
+
}));
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<template>
|
|
110
|
+
<div ref="$container" class="infinite-query-wrapper" :style="containerCssVars">
|
|
111
|
+
<div class="list">
|
|
112
|
+
<template v-for="(page) of pages">
|
|
113
|
+
<template v-for="record of page.list">
|
|
114
|
+
<!-- eslint-disable-next-line vue/no-extra-parens -->
|
|
115
|
+
<slot name="default" :record="(record as TRecord)" />
|
|
116
|
+
</template>
|
|
117
|
+
</template>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<slot
|
|
121
|
+
name="status" v-bind="slotProvide"
|
|
122
|
+
>
|
|
123
|
+
<slot name="initialLoading">
|
|
124
|
+
<div v-if="isInitialLoading" class="initial-loading">
|
|
125
|
+
<i class="i-svg-spinners:180-ring-with-bg loading-icon" />
|
|
126
|
+
</div>
|
|
127
|
+
</slot>
|
|
128
|
+
|
|
129
|
+
<slot name="initialLoadingError">
|
|
130
|
+
<div v-if="isInitialLoadingError" class="initial-loading-error" @click="refetch()">
|
|
131
|
+
<span>加载失败,点击重试</span>
|
|
132
|
+
</div>
|
|
133
|
+
</slot>
|
|
134
|
+
|
|
135
|
+
<slot name="empty">
|
|
136
|
+
<div v-if="isEmpty" class="empty">
|
|
137
|
+
<span>暂无数据</span>
|
|
138
|
+
</div>
|
|
139
|
+
</slot>
|
|
140
|
+
|
|
141
|
+
<slot name="oreLoading">
|
|
142
|
+
<div v-if="isMoreLoading" class="more-loading">
|
|
143
|
+
<i class="i-svg-spinners:180-ring-with-bg loading-icon" />
|
|
144
|
+
<span class="ml-1 text">加载中...</span>
|
|
145
|
+
</div>
|
|
146
|
+
</slot>
|
|
147
|
+
|
|
148
|
+
<slot name="moreLoadingError">
|
|
149
|
+
<div v-if="isMoreLoadingError" class="more-loading-error" @click="refetch()">
|
|
150
|
+
<span class="text">加载失败,点击重试</span>
|
|
151
|
+
</div>
|
|
152
|
+
</slot>
|
|
153
|
+
|
|
154
|
+
<slot name="noMore">
|
|
155
|
+
<div v-if="isNoMore" class="no-more" @click="refetch()">
|
|
156
|
+
<span class="text">暂无更多</span>
|
|
157
|
+
</div>
|
|
158
|
+
</slot>
|
|
159
|
+
</slot>
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|
|
162
|
+
|
|
163
|
+
<style scoped lang="scss">
|
|
164
|
+
.infinite-query-wrapper {
|
|
165
|
+
overflow: auto;
|
|
166
|
+
font-size: 14px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.loading-icon {
|
|
170
|
+
display: block;
|
|
171
|
+
color: var(--antd-colorPrimary);
|
|
172
|
+
font-size: 1.2em;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.initial-loading,
|
|
176
|
+
.initial-loading-error,
|
|
177
|
+
.empty,
|
|
178
|
+
.more-loading,
|
|
179
|
+
.more-loading-error,
|
|
180
|
+
.no-more {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
color: var(--antd-colorTextSecondary);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.initial-loading,
|
|
188
|
+
.initial-loading-error {
|
|
189
|
+
height: var(--ctn-height, 100px);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.initial-loading-error {
|
|
193
|
+
cursor: pointer;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.more-loading,
|
|
197
|
+
.more-loading-error,
|
|
198
|
+
.no-more {
|
|
199
|
+
height: 50px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.more-loading-error {
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { onUnmounted, watch } from 'vue';
|
|
2
|
-
import type { Ref } from 'vue';
|
|
3
|
-
import { useIntersectionObserver, useIntervalFn } from '@vueuse/core';
|
|
4
|
-
|
|
5
|
-
export function useCreateTrigger(containerEle: Ref<HTMLElement | undefined>, callback: Function, distance: string) {
|
|
6
|
-
const triggerEl = document.createElement('div');
|
|
7
|
-
|
|
8
|
-
Object.assign(triggerEl.style, {
|
|
9
|
-
position: 'relative',
|
|
10
|
-
zIndex: '5',
|
|
11
|
-
marginTop: `-${distance}`,
|
|
12
|
-
marginBottom: distance,
|
|
13
|
-
width: '5px',
|
|
14
|
-
height: '5px',
|
|
15
|
-
// background: 'red',
|
|
16
|
-
} satisfies Partial<CSSStyleDeclaration>);
|
|
17
|
-
|
|
18
|
-
useIntervalFn(() => {
|
|
19
|
-
const { transform } = triggerEl.style;
|
|
20
|
-
|
|
21
|
-
if (transform)
|
|
22
|
-
triggerEl.style.removeProperty('transform');
|
|
23
|
-
else triggerEl.style.setProperty('transform', 'translateX(-200%)');
|
|
24
|
-
}, 500);
|
|
25
|
-
|
|
26
|
-
useIntersectionObserver(triggerEl, ([entry]) => {
|
|
27
|
-
if (entry.isIntersecting)
|
|
28
|
-
callback();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
watch(containerEle, (ele) => {
|
|
32
|
-
if (ele)
|
|
33
|
-
ele.append(triggerEl);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
onUnmounted(() => {
|
|
37
|
-
triggerEl.remove();
|
|
38
|
-
});
|
|
39
|
-
}
|
|
1
|
+
import { onUnmounted, watch } from 'vue';
|
|
2
|
+
import type { Ref } from 'vue';
|
|
3
|
+
import { useIntersectionObserver, useIntervalFn } from '@vueuse/core';
|
|
4
|
+
|
|
5
|
+
export function useCreateTrigger(containerEle: Ref<HTMLElement | undefined>, callback: Function, distance: string) {
|
|
6
|
+
const triggerEl = document.createElement('div');
|
|
7
|
+
|
|
8
|
+
Object.assign(triggerEl.style, {
|
|
9
|
+
position: 'relative',
|
|
10
|
+
zIndex: '5',
|
|
11
|
+
marginTop: `-${distance}`,
|
|
12
|
+
marginBottom: distance,
|
|
13
|
+
width: '5px',
|
|
14
|
+
height: '5px',
|
|
15
|
+
// background: 'red',
|
|
16
|
+
} satisfies Partial<CSSStyleDeclaration>);
|
|
17
|
+
|
|
18
|
+
useIntervalFn(() => {
|
|
19
|
+
const { transform } = triggerEl.style;
|
|
20
|
+
|
|
21
|
+
if (transform)
|
|
22
|
+
triggerEl.style.removeProperty('transform');
|
|
23
|
+
else triggerEl.style.setProperty('transform', 'translateX(-200%)');
|
|
24
|
+
}, 500);
|
|
25
|
+
|
|
26
|
+
useIntersectionObserver(triggerEl, ([entry]) => {
|
|
27
|
+
if (entry.isIntersecting)
|
|
28
|
+
callback();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
watch(containerEle, (ele) => {
|
|
32
|
+
if (ele)
|
|
33
|
+
ele.append(triggerEl);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
onUnmounted(() => {
|
|
37
|
+
triggerEl.remove();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -2,7 +2,13 @@ import { ref } from 'vue';
|
|
|
2
2
|
import type { Component } from 'vue';
|
|
3
3
|
import type { ComponentExposed } from 'vue-component-type-helpers';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* 组件引用
|
|
7
|
+
* @param component 组件实例
|
|
8
|
+
* @returns 组件引用的代理对象
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
11
|
+
export function useComponentRef<C extends Component>(component: C) {
|
|
6
12
|
const _cpt = ref<any>();
|
|
7
13
|
const refFn = (cpt: any) => {
|
|
8
14
|
_cpt.value = cpt;
|
package/vue/hooks/useIsDark.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { onMounted, ref } from 'vue';
|
|
1
|
+
import { h, onMounted, ref } from 'vue';
|
|
2
|
+
import type { VNode } from 'vue';
|
|
3
|
+
import type { ArrayValues } from 'type-fest';
|
|
4
|
+
import { randomString } from '../../utils';
|
|
2
5
|
|
|
6
|
+
/**
|
|
7
|
+
* 使用选择器获取传送目标
|
|
8
|
+
* @param selectors 选择器字符串
|
|
9
|
+
* @returns 传送目标的引用
|
|
10
|
+
*/
|
|
3
11
|
export function useTeleportTarget(selectors: string) {
|
|
4
12
|
const target = ref<HTMLElement | null>();
|
|
5
13
|
|
|
@@ -10,4 +18,29 @@ export function useTeleportTarget(selectors: string) {
|
|
|
10
18
|
});
|
|
11
19
|
|
|
12
20
|
return target;
|
|
13
|
-
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 基于提供的顶级类和目标创建传送门选择器
|
|
25
|
+
* @param topClass 传送门选择器的顶级类
|
|
26
|
+
* @param targets 传送门的目标
|
|
27
|
+
*/
|
|
28
|
+
export function createTeleportSelectors<TG extends string[]>(topClass: string, targets: TG) {
|
|
29
|
+
type TGK = ArrayValues<TG>;
|
|
30
|
+
type TRet = { topClass: string }
|
|
31
|
+
& { [K in `${TGK}Selector`]: string }
|
|
32
|
+
& { [K in `${TGK}Class`]: string }
|
|
33
|
+
& { [K in `${TGK}Target`]: (className?: string, tag?: string) => VNode };
|
|
34
|
+
|
|
35
|
+
const ret: Record<string, any> = {};
|
|
36
|
+
ret.topClass = `${topClass}-${randomString(6)}`;
|
|
37
|
+
|
|
38
|
+
targets.forEach((target) => {
|
|
39
|
+
const _target = ret[`${target}Class`] = `${target}_${randomString(6)}`;
|
|
40
|
+
|
|
41
|
+
ret[`${target}Selector`] = `.${ret.topClass} .${_target}`;
|
|
42
|
+
ret[`${target}Target`] = (className: string = '', tag: string = 'div') => h(tag, { className: `${_target} ${className}` });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return ret as TRet;
|
|
46
|
+
}
|
package/vue/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useComponentRef } from './hooks/useComponentRef';
|
|
2
|
-
export { useTeleportTarget } from './hooks/useTeleportTarget';
|
|
2
|
+
export { useTeleportTarget, createTeleportSelectors } from './hooks/useTeleportTarget';
|
|
3
3
|
export { useIsMounted } from './hooks/useIsMounted';
|
|
4
4
|
export { useIsDark } from './hooks/useIsDark';
|
|
5
5
|
export { InfiniteQuery } from './components/infinite-query';
|