@peng_kai/kit 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/antd/admin/FilterDrawer.vue +95 -0
- package/antd/admin/FilterParam.vue +76 -0
- package/antd/admin/FilterReset.vue +22 -0
- package/antd/admin/index.ts +3 -0
- package/antd/hooks/createAntdModal.ts +61 -0
- package/antd/hooks/useAntdDrawer.ts +73 -0
- package/antd/hooks/useAntdForm.helpers.ts +48 -0
- package/antd/hooks/useAntdForm.ts +91 -0
- package/antd/hooks/useAntdModal.ts +65 -0
- package/antd/hooks/useAntdTable.ts +70 -0
- package/antd/index.ts +5 -0
- package/component/infinite-query/index.ts +1 -0
- package/component/infinite-query/src/InfiniteQuery.vue +147 -0
- package/component/infinite-query/src/useCreateTrigger.ts +35 -0
- package/package.json +38 -0
- package/pnpm-lock.yaml +586 -0
- package/request/helpers.ts +52 -0
- package/request/index.ts +0 -0
- package/request/interceptors/checkCode.ts +25 -0
- package/request/interceptors/formatPaging.ts +23 -0
- package/request/interceptors/index.ts +5 -0
- package/request/interceptors/popupMessage.ts +47 -0
- package/request/interceptors/returnResultType.ts +30 -0
- package/request/interceptors/unitizeAxiosError.ts +20 -0
- package/request/request.ts +89 -0
- package/request/type.d.ts +89 -0
- package/tsconfig.json +18 -0
- package/vue/hooks/useComponentRef.ts +27 -0
- package/vue/hooks/useTeleportTarget.ts +13 -0
- package/vue/index.ts +2 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
|
|
4
|
+
const emits = defineEmits<{
|
|
5
|
+
(e: 'filter'): void
|
|
6
|
+
(e: 'reset', value: number): void
|
|
7
|
+
}>()
|
|
8
|
+
const filterVisible = ref(false)
|
|
9
|
+
|
|
10
|
+
function filter() {
|
|
11
|
+
emits('filter')
|
|
12
|
+
filterVisible.value = false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function reset() {
|
|
16
|
+
emits('reset', 1)
|
|
17
|
+
filterVisible.value = false
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div class="p-3 mb-2 bg-white text-14px" @click="filterVisible = true">
|
|
23
|
+
<!-- .filter-params 为空时显示 .filter-params-tips -->
|
|
24
|
+
<div class="filter-params">
|
|
25
|
+
<slot name="params" />
|
|
26
|
+
</div>
|
|
27
|
+
<div class="filter-params-tips">
|
|
28
|
+
条件筛选,点击打开
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<ADrawer v-model:open="filterVisible" class="filter-drawer" placement="bottom" height="50vh">
|
|
33
|
+
<template #extra>
|
|
34
|
+
<AButton class="mr-3 my--3" @click="reset()">
|
|
35
|
+
重置
|
|
36
|
+
</AButton>
|
|
37
|
+
<AButton class="my--3" type="primary" @click="filter()">
|
|
38
|
+
筛选
|
|
39
|
+
</AButton>
|
|
40
|
+
</template>
|
|
41
|
+
<template #default>
|
|
42
|
+
<slot />
|
|
43
|
+
</template>
|
|
44
|
+
</ADrawer>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<style scoped lang="scss">
|
|
48
|
+
.filter-params {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
justify-content: flex-start;
|
|
52
|
+
gap: 5px 15px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// .filter-params 为空时显示 .filter-params-tips
|
|
56
|
+
.filter-params-tips {
|
|
57
|
+
display: none;
|
|
58
|
+
color: theme('colors.gray.DEFAULT');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.filter-params:empty {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.filter-params:empty + .filter-params-tips {
|
|
66
|
+
display: block;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
|
|
70
|
+
<style lang="scss">
|
|
71
|
+
.filter-drawer {
|
|
72
|
+
.ant-drawer-header {
|
|
73
|
+
padding: 16px;
|
|
74
|
+
|
|
75
|
+
.ant-drawer-close {
|
|
76
|
+
--expand: 5px;
|
|
77
|
+
|
|
78
|
+
padding: var(--expand);
|
|
79
|
+
margin: calc(var(--expand) * -1) var(--expand) calc(var(--expand) * -1) 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ant-drawer-body {
|
|
84
|
+
padding: 16px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ant-form-item {
|
|
88
|
+
margin-bottom: 0;
|
|
89
|
+
|
|
90
|
+
.ant-form-item-label {
|
|
91
|
+
padding-bottom: 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import isNil from 'lodash-es/isNil'
|
|
3
|
+
import isFinite from 'lodash-es/isFinite'
|
|
4
|
+
import dayjs from 'dayjs'
|
|
5
|
+
import bignumber from 'bignumber.js'
|
|
6
|
+
|
|
7
|
+
export const paramTypes = { numberRange, datetimeRange, options }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 时间范围格式化
|
|
11
|
+
* @param range 数字范围
|
|
12
|
+
* @param unit 单位
|
|
13
|
+
*/
|
|
14
|
+
function numberRange(range?: [number, number], unit?: string) {
|
|
15
|
+
if (!range?.every(isFinite))
|
|
16
|
+
return ''
|
|
17
|
+
|
|
18
|
+
return `${bignumber(range[0]).toFormat()}~${bignumber(range[1]).toFormat()}${unit}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 时间范围格式化
|
|
23
|
+
* @param range 时间范围
|
|
24
|
+
* @param template 格式化模板(文档:https://dayjs.gitee.io/docs/zh-CN/display/format )
|
|
25
|
+
*/
|
|
26
|
+
function datetimeRange(range?: [string | dayjs.Dayjs, string | dayjs.Dayjs], template = 'YYYY-MM-DD') {
|
|
27
|
+
if (!range?.every(v => dayjs(v).isValid()))
|
|
28
|
+
return ''
|
|
29
|
+
|
|
30
|
+
return `${dayjs(range[0]).format(template)} ~ ${dayjs(range[1]).format(template)}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function options(
|
|
34
|
+
value?: string | number | Array<string | number>,
|
|
35
|
+
options?: Array<{ value: string | number; label: any }>,
|
|
36
|
+
) {
|
|
37
|
+
if (isNil(value) || isNil(options))
|
|
38
|
+
return
|
|
39
|
+
if (value === '')
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
const _value = Array.isArray(value) ? value : [value]
|
|
43
|
+
|
|
44
|
+
return options
|
|
45
|
+
.filter(o => _value.includes(o.value))
|
|
46
|
+
.map(o => o.label)
|
|
47
|
+
.join(', ')
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
const props = defineProps<{
|
|
53
|
+
label: string
|
|
54
|
+
content?: any
|
|
55
|
+
}>()
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div v-if="props.content" class="item-param">
|
|
60
|
+
<span class="label">{{ props.label }}</span>
|
|
61
|
+
<span class="content">{{ props.content }}</span>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<style lang="scss" scoped>
|
|
66
|
+
.label {
|
|
67
|
+
display: inline-block;
|
|
68
|
+
margin-right: 0.3em;
|
|
69
|
+
color: theme('colors.gray.DEFAULT');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.content {
|
|
73
|
+
color: theme('colors.primary.DEFAULT');
|
|
74
|
+
word-break: break-all;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
loading?: boolean
|
|
4
|
+
}>()
|
|
5
|
+
const emits = defineEmits<{
|
|
6
|
+
(e: 'filter'): void
|
|
7
|
+
(e: 'reset'): void
|
|
8
|
+
}>()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div class="flex w-min mb-6">
|
|
13
|
+
<AButton class="mr-2" type="primary" :loading="props.loading" @click="emits('filter')">
|
|
14
|
+
查询
|
|
15
|
+
</AButton>
|
|
16
|
+
<AButton :disabled="props.loading" @click="emits('reset')">
|
|
17
|
+
重置
|
|
18
|
+
</AButton>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createVNode, defineComponent, reactive, render as vueRender } from 'vue'
|
|
2
|
+
import { Modal as AntModal } from 'ant-design-vue'
|
|
3
|
+
import { tryOnUnmounted } from '@vueuse/core'
|
|
4
|
+
import type { VNode, Component } from 'vue'
|
|
5
|
+
import type { ModalProps } from 'ant-design-vue'
|
|
6
|
+
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
7
|
+
import type { Writable } from 'type-fest'
|
|
8
|
+
|
|
9
|
+
export function createAntdModal<Comp extends Component>(comp: Comp, defaultModalProps: ModalProps = {}) {
|
|
10
|
+
type TCompProps = Writable<ComponentProps<Comp>>
|
|
11
|
+
|
|
12
|
+
let holderUsed = false // 是否是使用 ContextHolder 的方式挂载弹窗
|
|
13
|
+
let container: DocumentFragment
|
|
14
|
+
let instance: VNode
|
|
15
|
+
const compProps = reactive({} as TCompProps)
|
|
16
|
+
const modalProps = reactive<ModalProps>({
|
|
17
|
+
'open': false,
|
|
18
|
+
'destroyOnClose': true,
|
|
19
|
+
...defaultModalProps,
|
|
20
|
+
'onUpdate:open': (value) => {
|
|
21
|
+
modalProps.open = value
|
|
22
|
+
},
|
|
23
|
+
'afterClose': () => {
|
|
24
|
+
container && vueRender(null, container as any);
|
|
25
|
+
(instance as any) = null;
|
|
26
|
+
(container as any) = null
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
const ContextHolder = defineComponent({
|
|
30
|
+
setup() {
|
|
31
|
+
if (!instance)
|
|
32
|
+
holderUsed = true
|
|
33
|
+
},
|
|
34
|
+
render() {
|
|
35
|
+
return createVNode(AntModal, modalProps, { modalRender: () => createVNode(comp, compProps) })
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const open = (newCompProps: TCompProps) => {
|
|
40
|
+
Object.keys(compProps).forEach(k => delete compProps[k])
|
|
41
|
+
Object.assign(compProps, {
|
|
42
|
+
...newCompProps,
|
|
43
|
+
onClose: close,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (!holderUsed) {
|
|
47
|
+
container = document.createDocumentFragment()
|
|
48
|
+
instance = createVNode(ContextHolder)
|
|
49
|
+
vueRender(instance, container as any)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
modalProps.open = true
|
|
53
|
+
}
|
|
54
|
+
const close = () => {
|
|
55
|
+
modalProps.open = false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tryOnUnmounted(close)
|
|
59
|
+
|
|
60
|
+
return { open, close, ContextHolder }
|
|
61
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Button as AButton, Drawer as ADrawer, Space as ASpace } from 'ant-design-vue'
|
|
2
|
+
import { defineComponent, isProxy, reactive, toRefs, toRef, h } from 'vue'
|
|
3
|
+
import type { Component } from 'vue'
|
|
4
|
+
import type { ButtonProps, DrawerProps } from 'ant-design-vue'
|
|
5
|
+
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
6
|
+
import type { Writable } from 'type-fest'
|
|
7
|
+
import { useComponentRef } from '../../vue'
|
|
8
|
+
|
|
9
|
+
const defaultDrawerProps: DrawerProps = { open: false, destroyOnClose: true }
|
|
10
|
+
|
|
11
|
+
interface IComponentConfig<Comp extends Component> {
|
|
12
|
+
is: Comp
|
|
13
|
+
props?: Writable<ComponentProps<Comp>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useAntdDrawer<Comp extends Component>(
|
|
17
|
+
comp: IComponentConfig<Comp> | Comp,
|
|
18
|
+
drawerProps = defaultDrawerProps,
|
|
19
|
+
) {
|
|
20
|
+
const _comp = ({ props: {}, ...((comp as any)?.is ? comp : { is: comp }) }) as Required<IComponentConfig<Comp>>
|
|
21
|
+
const compProps = reactive(_comp.props)
|
|
22
|
+
const compRef = useComponentRef(_comp.is)
|
|
23
|
+
const _drawerProps: DrawerProps = reactive({
|
|
24
|
+
...defaultDrawerProps,
|
|
25
|
+
...isProxy(drawerProps) ? toRefs(drawerProps) : drawerProps,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const open = (newBodyProps?: Partial<typeof compProps>, newAntdModalProps?: Omit<Partial<DrawerProps>, 'open'>) => {
|
|
29
|
+
Object.assign(_drawerProps, newAntdModalProps)
|
|
30
|
+
Object.assign(compProps, newBodyProps)
|
|
31
|
+
_drawerProps.open = true
|
|
32
|
+
}
|
|
33
|
+
const close = () => {
|
|
34
|
+
_drawerProps.open = false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DrawerExtra = defineComponent({
|
|
38
|
+
setup() {
|
|
39
|
+
const cancelBtnProps: ButtonProps = reactive({ onClick: close })
|
|
40
|
+
const confirmBtnProps: ButtonProps = reactive({
|
|
41
|
+
type: 'primary',
|
|
42
|
+
loading: toRef(() => (compRef as any)?.loading),
|
|
43
|
+
onClick: () => (compRef as any)?.confirm?.(),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return { cancelBtnProps, confirmBtnProps }
|
|
47
|
+
},
|
|
48
|
+
render() {
|
|
49
|
+
const { cancelBtnProps, confirmBtnProps } = this
|
|
50
|
+
|
|
51
|
+
return h(ASpace, {}, () => [
|
|
52
|
+
h(AButton, cancelBtnProps, () => '取消'),
|
|
53
|
+
h(AButton, confirmBtnProps, () => '确定'),
|
|
54
|
+
])
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
const PresetComponent = defineComponent({
|
|
58
|
+
render() {
|
|
59
|
+
return h(ADrawer, _drawerProps, {
|
|
60
|
+
default: () => h(_comp.is, compProps as any),
|
|
61
|
+
})
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
_drawerProps.extra = _drawerProps.extra === undefined ? h(DrawerExtra) : _drawerProps.extra
|
|
66
|
+
_drawerProps['onUpdate:open'] = (visiable) => {
|
|
67
|
+
_drawerProps.open = visiable
|
|
68
|
+
}
|
|
69
|
+
(compProps as any).ref = compRef;
|
|
70
|
+
(compProps as any).onClose = close
|
|
71
|
+
|
|
72
|
+
return { PresetComponent, drawerProps: _drawerProps, open, close }
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export { buildGroupFieldName, parseGroupFieldName, isSameGroup, isSameField, getGroupIndex, GROUP_SEP }
|
|
2
|
+
|
|
3
|
+
const GROUP_SEP = '__'
|
|
4
|
+
const groupFieldNameRE = new RegExp(
|
|
5
|
+
`(?<groupName>[a-zA-Z0-9]+)${GROUP_SEP}(?<index>\\d+)${GROUP_SEP}(?<fieldName>[a-zA-Z0-9]+)`,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
function buildGroupFieldName(name: string, index: number, field: string) {
|
|
9
|
+
return name + GROUP_SEP + index + GROUP_SEP + field
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function parseGroupFieldName(name: string) {
|
|
13
|
+
const res = name.match(groupFieldNameRE)
|
|
14
|
+
const ret = {
|
|
15
|
+
fieldName: '',
|
|
16
|
+
index: -1,
|
|
17
|
+
groupName: '',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (res?.groups) {
|
|
21
|
+
ret.fieldName = res.groups.fieldName
|
|
22
|
+
ret.index = Number(res.groups.index)
|
|
23
|
+
ret.groupName = res.groups.groupName
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ret
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getGroupIndex(name: string, schemas: Record<string, any>) {
|
|
30
|
+
const indexs = Object.keys(schemas).map((key) => {
|
|
31
|
+
const { groupName, index } = parseGroupFieldName(key)
|
|
32
|
+
return groupName === name ? index : -1
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return Math.max.apply(undefined, indexs)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isSameGroup(name: string, groupName: string) {
|
|
39
|
+
const { groupName: gn } = parseGroupFieldName(name)
|
|
40
|
+
|
|
41
|
+
return gn === groupName
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isSameField(name: string, fieldName: string) {
|
|
45
|
+
const { fieldName: fn } = parseGroupFieldName(name)
|
|
46
|
+
|
|
47
|
+
return fn === fieldName
|
|
48
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { reactiveComputed } from '@vueuse/core'
|
|
2
|
+
import type { FormInstance, FormItemProps, FormProps, RuleObject } from 'ant-design-vue/es/form'
|
|
3
|
+
import { ref, computed } from 'vue'
|
|
4
|
+
import type { ComputedRef, UnwrapRef } from 'vue'
|
|
5
|
+
import { GROUP_SEP, buildGroupFieldName, getGroupIndex } from './useAntdForm.helpers'
|
|
6
|
+
|
|
7
|
+
export { useAntdForm }
|
|
8
|
+
export type { TField }
|
|
9
|
+
|
|
10
|
+
function useAntdForm<F extends Record<string, TField>, S extends GetFormState<F>, STF = S>(
|
|
11
|
+
schemas: F,
|
|
12
|
+
_options?: {
|
|
13
|
+
transform?: (state: S) => STF
|
|
14
|
+
},
|
|
15
|
+
) {
|
|
16
|
+
const formRef = ref<FormInstance>()
|
|
17
|
+
const $form = {
|
|
18
|
+
get clearValidate() {
|
|
19
|
+
return formRef.value?.clearValidate
|
|
20
|
+
},
|
|
21
|
+
get getFieldsValue() {
|
|
22
|
+
return formRef.value?.getFieldsValue
|
|
23
|
+
},
|
|
24
|
+
get resetFields() {
|
|
25
|
+
return formRef.value?.resetFields
|
|
26
|
+
},
|
|
27
|
+
get scrollToField() {
|
|
28
|
+
return formRef.value?.scrollToField
|
|
29
|
+
},
|
|
30
|
+
get validate() {
|
|
31
|
+
return formRef.value?.validate
|
|
32
|
+
},
|
|
33
|
+
get validateFields() {
|
|
34
|
+
return formRef.value?.validateFields
|
|
35
|
+
},
|
|
36
|
+
setField(name: string, schema: TField) {
|
|
37
|
+
(schemas as any)[name] = schema
|
|
38
|
+
},
|
|
39
|
+
addGroup(groupName: string, groupSchemas: Record<string, TField>) {
|
|
40
|
+
const i = getGroupIndex(groupName, schemas) + 1
|
|
41
|
+
|
|
42
|
+
Object.keys(groupSchemas).forEach((key) => {
|
|
43
|
+
const name = buildGroupFieldName(groupName, i, key);
|
|
44
|
+
(schemas as any)[name] = groupSchemas[key]
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
removeGroup(groupName: string, index?: number) {
|
|
48
|
+
const i = index ?? getGroupIndex(groupName, schemas)
|
|
49
|
+
|
|
50
|
+
Object.keys(schemas).forEach((key) => {
|
|
51
|
+
if (key.startsWith(groupName + GROUP_SEP + i))
|
|
52
|
+
Reflect.deleteProperty(schemas, key)
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
const state = reactiveComputed(() => {
|
|
57
|
+
return Object.fromEntries(Object.entries(schemas).map(([k, v]) => [k, v.value])) as S
|
|
58
|
+
})
|
|
59
|
+
const props = reactiveComputed<FormProps>(() => {
|
|
60
|
+
return {
|
|
61
|
+
model: state,
|
|
62
|
+
ref: (c: any) => (formRef.value = c),
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
const itemProps = reactiveComputed(() => {
|
|
66
|
+
const propsTuple = Object.entries(schemas).map(([key, value]): [string, FormItemProps] => [
|
|
67
|
+
key,
|
|
68
|
+
{ name: key, rules: value.rules },
|
|
69
|
+
])
|
|
70
|
+
const props = Object.fromEntries(propsTuple) as Record<keyof S, FormItemProps>
|
|
71
|
+
|
|
72
|
+
return props
|
|
73
|
+
})
|
|
74
|
+
const stateTF = _options?.transform ? computed(() => _options.transform!(state)) : computed(() => state)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
$form,
|
|
78
|
+
state,
|
|
79
|
+
stateTF: stateTF as ComputedRef<STF>,
|
|
80
|
+
props,
|
|
81
|
+
itemProps,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface TField {
|
|
86
|
+
value: any
|
|
87
|
+
rules?: RuleObject[]
|
|
88
|
+
}
|
|
89
|
+
type GetFormState<C extends Record<string, TField>> = {
|
|
90
|
+
[K in keyof C]: UnwrapRef<C[K]['value']>;
|
|
91
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Modal as AntModal } from 'ant-design-vue'
|
|
2
|
+
import { createVNode, defineComponent, isProxy, reactive, toRefs, toRef } from 'vue'
|
|
3
|
+
import type { Component } from 'vue'
|
|
4
|
+
import type { ModalProps } from 'ant-design-vue'
|
|
5
|
+
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
6
|
+
import type { Writable } from 'type-fest'
|
|
7
|
+
import { useComponentRef } from '../../vue'
|
|
8
|
+
|
|
9
|
+
const defaultModalProps: ModalProps = { open: false, destroyOnClose: true, wrapClassName: 'antd-cover__basic-modal' }
|
|
10
|
+
|
|
11
|
+
interface IComponentConfig<Comp extends Component> {
|
|
12
|
+
is: Comp
|
|
13
|
+
props?: Writable<ComponentProps<Comp>>
|
|
14
|
+
type?: 'modal' | 'body'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AntdModal 组件的辅助 Hook
|
|
19
|
+
* @param comp 组件配置,也可以直接传入组件,既 comp.is 参数
|
|
20
|
+
* @param comp.is 组件
|
|
21
|
+
* @param comp.props 组件的 Props
|
|
22
|
+
* @param comp.type 组件类型,modal 为弹窗组件,body 为弹窗内容组件
|
|
23
|
+
* @param modalProps AntdModal 组件的 Props
|
|
24
|
+
*/
|
|
25
|
+
export function useAntdModal<Comp extends Component>(
|
|
26
|
+
comp: IComponentConfig<Comp> | Comp,
|
|
27
|
+
modalProps = defaultModalProps,
|
|
28
|
+
) {
|
|
29
|
+
const _comp = ({ props: {}, type: 'body', ...((comp as any)?.is ? comp : { is: comp }) }) as Required<IComponentConfig<Comp>>
|
|
30
|
+
const compProps = reactive(_comp.props)
|
|
31
|
+
const compRef = useComponentRef(_comp.is)
|
|
32
|
+
const _modalProps: ModalProps = reactive({
|
|
33
|
+
...defaultModalProps,
|
|
34
|
+
...isProxy(modalProps) ? toRefs(modalProps) : modalProps,
|
|
35
|
+
confirmLoading: toRef(() => (compRef as any)?.loading),
|
|
36
|
+
onOk: () => (compRef as any)?.confirm?.(),
|
|
37
|
+
})
|
|
38
|
+
const modalSlotName = _comp.type === 'body' ? 'default' : 'modalRender'
|
|
39
|
+
|
|
40
|
+
const open = (newCompProps?: Partial<typeof compProps>, newAntdModalProps?: Omit<Partial<ModalProps>, 'open'>) => {
|
|
41
|
+
Object.assign(_modalProps, newAntdModalProps)
|
|
42
|
+
Object.assign(compProps, newCompProps)
|
|
43
|
+
|
|
44
|
+
_modalProps.open = true
|
|
45
|
+
}
|
|
46
|
+
const close = () => {
|
|
47
|
+
_modalProps.open = false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PresetComponent = defineComponent({
|
|
51
|
+
render() {
|
|
52
|
+
return createVNode(AntModal, _modalProps, {
|
|
53
|
+
[modalSlotName]: () => createVNode(_comp.is, compProps as any),
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
_modalProps['onUpdate:open'] = (visiable) => {
|
|
59
|
+
_modalProps.open = visiable
|
|
60
|
+
}
|
|
61
|
+
(compProps as any).ref = compRef;
|
|
62
|
+
(compProps as any).onClose = close
|
|
63
|
+
|
|
64
|
+
return { PresetComponent, modalProps: _modalProps, compProps, $comp: compRef, open, close }
|
|
65
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import type { UseQueryReturnType } from '@tanstack/vue-query'
|
|
3
|
+
import type { Table, TableProps } from 'ant-design-vue'
|
|
4
|
+
import type { ColumnType } from 'ant-design-vue/es/table/interface'
|
|
5
|
+
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
6
|
+
|
|
7
|
+
export function useAntdTable<
|
|
8
|
+
UQRR extends UseQueryReturnType<any, any>,
|
|
9
|
+
QP extends Partial<{ page?: string | number; page_size?: string | number }>,
|
|
10
|
+
>(uqrt: UQRR, queryParams: QP) {
|
|
11
|
+
type RecordType = GetRecordType<UQRR>
|
|
12
|
+
type LocalTableProps = TableProps<RecordType>
|
|
13
|
+
type LocalColumnsType = NonNullable<LocalTableProps['columns']>
|
|
14
|
+
|
|
15
|
+
const { data, isFetching, isLoading } = uqrt
|
|
16
|
+
|
|
17
|
+
const onPaginationChange: ComponentProps<typeof Table>['onChange'] = (pagination) => {
|
|
18
|
+
const page = queryParams.page_size !== pagination.pageSize ? 1 : pagination.current
|
|
19
|
+
Object.assign(queryParams, { page, page_size: pagination.pageSize ?? 10 })
|
|
20
|
+
}
|
|
21
|
+
const defineColumns = (columnsFn: () => LocalColumnsType) => computed(columnsFn)
|
|
22
|
+
|
|
23
|
+
const tableProps = computed<LocalTableProps>(() => {
|
|
24
|
+
const { list, pagination } = data.value ?? {}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
dataSource: list,
|
|
28
|
+
pagination: {
|
|
29
|
+
disabled: isFetching.value,
|
|
30
|
+
current: Number(queryParams.page ?? 1),
|
|
31
|
+
pageSize: Number(queryParams.page_size ?? 10),
|
|
32
|
+
total: pagination?.total ?? 0,
|
|
33
|
+
},
|
|
34
|
+
loading: isLoading.value,
|
|
35
|
+
scroll: { x: 'max-content' },
|
|
36
|
+
sticky: true,
|
|
37
|
+
onChange: onPaginationChange as any,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
const dataIndexs = new Proxy({} as Record<keyof RecordType, string>, {
|
|
41
|
+
get(_, p) {
|
|
42
|
+
return p
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
const bodyCellType = {} as {
|
|
46
|
+
index: number
|
|
47
|
+
text: any
|
|
48
|
+
value: any
|
|
49
|
+
record: RecordType
|
|
50
|
+
column: ColumnType<RecordType>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
/** ATable 的预设 Props */
|
|
55
|
+
tableProps,
|
|
56
|
+
/** 【类型辅助】基于接口数据类型推导出的 dataIndex,供 columns 的 dataIndex 使用 */
|
|
57
|
+
dataIndexs,
|
|
58
|
+
/** 【类型辅助】bodyCell 插槽数据的精确类型描述 */
|
|
59
|
+
bodyCellType,
|
|
60
|
+
/** 【类型辅助】用于定义出类型精确的 columns */
|
|
61
|
+
defineColumns,
|
|
62
|
+
onPaginationChange,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type GetRecordType<T> = T extends UseQueryReturnType<infer D, any>
|
|
67
|
+
? D extends Api.PageData
|
|
68
|
+
? NonNullable<D['list']>[0]
|
|
69
|
+
: never
|
|
70
|
+
: never
|
package/antd/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { useAntdModal } from './hooks/useAntdModal'
|
|
2
|
+
export { useAntdDrawer } from './hooks/useAntdDrawer'
|
|
3
|
+
export { useAntdTable } from './hooks/useAntdTable'
|
|
4
|
+
export { useAntdForm } from './hooks/useAntdForm'
|
|
5
|
+
export { createAntdModal } from './hooks/createAntdModal'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InfiniteQuery } from './src/InfiniteQuery.vue'
|