夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • 博客搭建

  • Ajax

  • Vite

  • Vitest

  • Nuxt

  • UI库文章

    • iconify
    • elelmentPlus
    • echarts的使用
    • antDesign
    • antDesignVue
      • 封装table分页
      • 封装获取table高度
      • 封装upload
      • 封装form
  • Docker

  • 技术
  • UI库文章
夜猫子
2023-07-20
目录

antDesignVue

# 封装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

# 封装获取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

# 封装upload

1.attrs 属性不能被响应式监听

2.同名属性(比如 customRequest),attrs 中的属性会覆盖掉你封装的

3.props 中定义的属性不会再出现在 attrs 中

3.attrs 中不允许出现同名事件,同名事件会被合并成一个数组然后报错了:

image-20241108165158556

<template>
  <Upload
    v-bind="attrs"
    :class="{ 'mt--8': disabled }"
    ref="componentRef"
    v-model:file-list="fileList"
    :customRequest="customUpload"
    :disabled="disabled"
    :beforeUpload="(...args) => beforeUpload(...[...args, props])"
    @change="handleChange"
  >
    <!-- 遍历子组件作用域插槽,并对父组件暴露 -->
    <template v-for="k in Object.keys(slots)" #[k]="slotProps" :key="k">
      <slot :name="k" v-bind="slotProps || {}">
        <template v-if="slots[k]?.()">
          <v-render :content="slots[k]?.()" />
        </template>
      </slot>
    </template>
  </Upload>
</template>

<script setup>
import { Upload } from 'ant-design-vue'
import { useAttrs, useSlots, watch, defineModel, useTemplateRef, h } from 'vue'
import { fileUpload, defaultBeforeUpload } from './config'
import SubmitButton from './SubmitButton'

const attrs = useAttrs()
const emits = defineEmits(['change'])
const props = defineProps({
  disabled: {
    type: Boolean,
    default: false,
  },
  maxSize: {
    type: Number,
  },
  requestConfig: {
    type: Object,
  },
  sizeUnit: { type: String, default: 'MB' }, // 文件大小单位: Byte  KB  MB  GB  TB
  beforeUpload: {
    type: Function,
    default: defaultBeforeUpload,
  },
})
const slots = {
  default: () => {
    if (!props.disabled) {
      return h(SubmitButton)
    }
  },
  ...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,
      }))
    } else {
      fileList.value = []
    }
  },
  {
    immediate: true,
    deep: true,
  },
)

const componentRef = useTemplateRef('componentRef')

const customUpload = async (e) => {
  const { onProgress, file, onSuccess, onError } = e
  const formData = new FormData()
  formData.append('file', file)
  fileUpload(formData, {
    ...props.requestConfig,
    onUploadProgress: ({ total, loaded }) => {
      onProgress({ percent: Number(Math.round((loaded / total) * 100).toFixed(2)) }, file)
    },
  })
    .then((data = {}) => {
      onSuccess(
        {
          ...data,
          name: data.name,
          url: data.link,
          status: 'done',
        },
        file,
      )
    })
    .catch(onError)
}

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
117
118
119
120
121
122
123
124
125

config.js

import request from '@/axios/request'
import { message, Upload } from 'ant-design-vue'

export const fileUpload = (data, config) => {
  return request({
    method: 'post',
    url: `/gateway/hc-resource/oss/endpoint/put-file`,
    data,
    ...config,
  })
}

const sizeUnitMap = { Byte: 0, KB: 1, MB: 2, GB: 3, TB: 4 }
export const defaultBeforeUpload = (file, options, limit) => {
  const maxSize = limit.maxSize * Math.pow(1024, sizeUnitMap[limit.sizeUnit])
  if (file.size > maxSize) {
    message.info({
      content: `文件大小不能超过 ${limit.maxSize}${limit.sizeUnit}`,
      key: 'upload_info',
    })
    return Upload.LIST_IGNORE
  }
  return true
}

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

SubmitButton.jsx

import { defineComponent } from 'vue'
import { UploadOutlined } from '@ant-design/icons-vue'

const SubmitButton = defineComponent({
  render(ctx) {
    return (
      <a-button>
        <UploadOutlined></UploadOutlined>
        上传文件
      </a-button>
    )
  },
})


export default SubmitButton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 封装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
// 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
编辑 (opens new window)
上次更新: 2025/5/7 18:16:53
antDesign
基础

← antDesign 基础→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式