# 封装table分页
export const usePagination = (options) => {
const { searchParams = {}, loadData, pageOptions, manual = false } = options
const list = ref([])
const loading = ref(false)
const pagination = reactive({
total: 0,
current: 1,
showTotal: (total) => `共 ${total} 条记录`,
pageSize: 10,
pageSizeOptions: ['10', '20', '30', '40', '50'],
showQuickJumper: true,
onChange,
...pageOptions
})
async function onChange(current, pageSize, params = searchParams) {
pagination.current = current
pagination.pageSize = pageSize
if (!loadData) return
let newSearchParams
if (typeof params === 'function') {
newSearchParams = params(pagination)
} else {
newSearchParams = unref(params)
}
loading.value = true
const { data = {} } = await loadData({
curPage: current,
pageSize,
...removeEmptyObjKey(newSearchParams)
}).finally(() => {
loading.value = false
})
const { total, records = [] } = data
pagination.total = total
list.value = records
}
if (!manual) {
onChange(pagination.current, pagination.pageSize, searchParams)
}
const search = (params = searchParams) => {
onChange(pagination.current, pagination.pageSize, params)
}
return { pagination, records: list, search, loading }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 封装获取table高度
import { reactive } from 'vue'
import { useRequest } from 'ym-userequest'
export const useTableHeight = (select, options = {}, requestOptions) => {
const { offset = 0 } = options
const getTableHeight = () => {
return new Promise((resolve, reject) => {
const targetElememt = document.querySelector(select)
targetElememt.classList.remove('overflow-hidden')
targetElememt.classList.add('overflow-hidden')
const tableBody = document.querySelector(`${select} .ant-table-body`)
nextTick(() => {
const tablepagination = document.querySelector(`${select} .ant-pagination`)
const tableHeader = document.querySelector(`${select} .ant-table-header`)
if (!tableHeader || !tableBody) {
reject('not find')
} else {
tableBody.style['max-height'] = '0px'
const tablepaginationHeight = tablepagination?.offsetHeight
? tablepagination?.offsetHeight + 32 // 32 是分页边距
: 0
const tableHeight =
targetElememt.offsetHeight - tableHeader.offsetHeight - tablepaginationHeight - offset
resolve(tableHeight)
}
})
})
}
const instance = useRequest(getTableHeight, {
defaultData: 500,
retryCount: 10,
retryInterval: 500,
onError() {},
...requestOptions,
})
let ticking = false
function changeHeight() {
if (ticking) return
ticking = true
// 使用动画帧优化监听
requestAnimationFrame(() => {
instance.refresh()
ticking = false
})
}
onMounted(() => {
window.addEventListener('resize', changeHeight)
})
onUnmounted(() => {
window.removeEventListener('resize', changeHeight)
})
return instance
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 封装upload
1.attrs 属性不能被响应式监听
2.同名属性(比如 customRequest),attrs 中的属性会覆盖掉你封装的
3.props 中定义的属性不会再出现在 attrs 中
3.attrs 中不允许出现同名事件,同名事件会被合并成一个数组然后报错了:
<template>
<a-upload
v-bind="attrs"
ref="componentRef"
v-model:file-list="fileList"
:customRequest="customUpload"
:disabled="disabled"
@change="handleChange"
>
<!-- 遍历子组件作用域插槽,并对父组件暴露 -->
<template v-for="k in Object.keys(slots)" #[k]="slotProps" :key="k">
<slot :name="k" v-bind="slotProps || {}">
<v-render :content="slots[k]?.()" />
</slot>
</template>
</a-upload>
</template>
<script setup lang="jsx">
// https://hj-dev.hczyun.cn/data/minio/leo/upload/20241108/475003d1441d36dd80912b9adffed9fd.jpg
import { useAttrs, useSlots, watch, defineModel, useTemplateRef } from 'vue'
import { UploadOutlined } from '@ant-design/icons-vue'
import { fileUpload } from './config'
const attrs = useAttrs()
const emits = defineEmits(['change'])
const props = defineProps({
disabled: {
type: Boolean,
default: false
}
})
const slots = {
default: (...args) => {
if (!props.disabled) {
return (
<a-button>
<UploadOutlined></UploadOutlined>
上传文件
</a-button>
)
}
},
...useSlots()
}
const fileList = defineModel('fileList', {
default: () => []
})
const files = defineModel('files', {
default: ''
})
watch(
() => files,
(newVal) => {
if (newVal.value?.length) {
fileList.value = newVal.value.split(',').map((v) => ({
uid: v,
name: v.split('/').slice(-1)[0],
url: v
}))
}
},
{
immediate: true
}
)
const componentRef = useTemplateRef('componentRef')
const customUpload = async (...args) => {
const e = args[0]
let file = e.file
let progress = { percent: 1 }
let speed = 100 / (file.size / 65000)
const intervalId = setInterval(() => {
if (progress.percent < 100) {
progress.percent += speed
e.onProgress(progress)
} else {
clearInterval(intervalId)
}
}, 100)
const formData = new FormData()
formData.append('file', file)
fileUpload(formData)
.then(({ data = {} }) => {
if (!data.link) return
e.onSuccess({
name: data.name,
url: data.link
})
})
.catch((err) => {
e.onError(err)
})
}
const handleChange = ({ file, fileList }) => {
if (file.status === 'done') {
const urls = fileList.map((v) => {
const url = v.url || v.response?.url
return url
})
files.value = urls.join(',')
}
emits('change', { file, fileList })
}
defineExpose({
componentRef
})
</script>
<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 封装form
<template>
<a-form v-bind="attrs" :model="model || form" ref="componentRef">
<!-- 遍历子组件作用域插槽,并对父组件暴露 -->
<template v-for="k in Object.keys(slots)" #[k]="slotProps" :key="k">
<slot :name="k" v-bind="slotProps || {}">
<v-render :content="slots[k]?.()" />
</slot>
</template>
</a-form>
</template>
<script setup lang="jsx">
import { useAttrs, useSlots, useTemplateRef } from 'vue'
import { useFormItems } from './hooks'
const attrs = useAttrs()
const form = defineModel('form')
const props = defineProps({
model: Object,
formItems: {
type: Array,
default: () => []
}
})
const slots = {
default: () => useFormItems(props.formItems, form),
...useSlots()
}
const componentRef = useTemplateRef('componentRef')
defineExpose({
componentRef
})
</script>
<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// hooks.js
import { h } from 'vue'
import { type } from '@/utils/tools'
import * as Ant from 'ant-design-vue'
import { getValueFromPath, setNestedValue } from '@/utils/tools'
export const useFormItems = (formItems, form) => {
const render = formItems.map((item, index) => {
const itemProps = item?.itemProps || {}
const bindValue =
itemProps.name && itemProps.bindType
? {
[itemProps.bindType]: getValueFromPath(form.value, itemProps.name),
[`onUpdate:${itemProps.bindType}`]: (value) =>
setNestedValue(form.value, itemProps.name, value),
}
: {}
const renderComponent = (component) => {
if (type.isArray(component)) {
const [name, props] = component
return h(Ant[name], { ...bindValue, ...props })
} else {
return h(component, bindValue)
}
}
let component = item.component ? renderComponent(item.component) : null
return (
<a-form-item {...itemProps} key={index}>
{component || <a-input v-model={[form.value[itemProps.name], 'value']} />}
</a-form-item>
)
})
return render
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37