# 封装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 = (id, offset = 0) => {
const getTable = () => {
return new Promise((resolve, reject) => {
try {
const targetElememt = document.getElementById(id)
const tableWraps = document.querySelectorAll('.ant-table-wrapper')
let tableWrap
tableWraps.forEach((d) => {
const table = d.querySelector(`#${id}`)
if (table) {
tableWrap = d
}
})
const tableHeader = document.querySelector(`#${id} .ant-table-header`)
const parentElement = targetElememt.parentElement
const tablepagination = parentElement.querySelector(`.ant-pagination`)
resolve(
tableWrap.offsetHeight -
(tableHeader?.offsetHeight || 0) -
(tablepagination?.offsetHeight || 0) -
offset
)
} catch (error) {
reject('未获取table')
}
})
}
const { data, run, cancel, pollingCount } = useRequest(getTable, {
defaultData: 500,
pollingInterval: 100,
onSuccess() {
cancel()
},
onError(err) {
if (pollingCount.value >= 10) {
cancel()
}
}
})
return { data, run, cancel }
}
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
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
# 封装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, h } from 'vue'
const attrs = useAttrs()
const slots = {
default: (...args) => {
return formItemsRender()
},
...useSlots()
}
const props = defineProps({
model: Object,
formItems: {
type: Array,
default: () => []
}
})
function getValueFromPath(obj, path) {
const newPath = path instanceof Array ? path : [path]
return newPath.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj)
}
function setNestedValue(obj, path, value) {
const newPath = path instanceof Array ? path : [path]
return newPath.reduce((acc, key, index, arr) => {
if (index === arr.length - 1) {
acc[key] = value // 最后一个元素,直接赋值
} else if (!acc[key]) {
acc[key] = {} // 如果属性不存在,则初始化为空对象(假设你要修改的是对象属性)
}
return acc[key] // 返回当前属性的引用,供下一次迭代使用
}, obj)
}
const form = defineModel('form')
const formItemsRender = () => {
const formItems = props.formItems
const render = formItems.map((item, index) => {
const bindValue =
item.itemProps.name && item.itemProps.bindType
? {
[item.itemProps.bindType]: getValueFromPath(form.value, item.itemProps.name),
[`onUpdate:${item.itemProps.bindType}`]: (value) =>
setNestedValue(form.value, item.itemProps.name, value)
}
: {}
let component = item.component ? h(item.component, bindValue) : null
return (
<a-form-item {...item.itemProps} key={index}>
{component || <a-input v-model={[form.value[item.itemProps.name], 'value']} />}
</a-form-item>
)
})
return render
}
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
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
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