Browse Source

chore: marge main

main
xingyu 2 years ago
parent
commit
84a4db58a9
  1. 2
      src/components/Application/src/search/AppSearchModal.vue
  2. 65
      src/components/Form/src/BasicForm.vue
  3. 51
      src/components/Form/src/components/ApiCascader.vue
  4. 42
      src/components/Form/src/components/ApiRadioGroup.vue
  5. 44
      src/components/Form/src/components/ApiSelect.vue
  6. 44
      src/components/Form/src/components/ApiTransfer.vue
  7. 30
      src/components/Form/src/components/ApiTree.vue
  8. 41
      src/components/Form/src/components/ApiTreeSelect.vue
  9. 38
      src/components/Form/src/components/FormAction.vue
  10. 100
      src/components/Form/src/components/FormItem.vue
  11. 18
      src/components/Form/src/components/RadioButtonGroup.vue
  12. 14
      src/components/Form/src/helper.ts
  13. 13
      src/components/Form/src/hooks/useAdvanced.ts
  14. 7
      src/components/Form/src/hooks/useAutoFocus.ts
  15. 10
      src/components/Form/src/hooks/useForm.ts
  16. 18
      src/components/Form/src/hooks/useFormEvents.ts
  17. 12
      src/components/Form/src/hooks/useFormValues.ts
  18. 7
      src/components/Form/src/hooks/useLabelWidth.ts
  19. 4
      src/components/Form/src/props.ts
  20. 44
      src/components/Form/src/types/form.ts
  21. 65
      src/components/Table/src/BasicTable.vue
  22. 19
      src/components/Table/src/components/TableAction.vue
  23. 2
      src/components/Table/src/components/TableFooter.vue
  24. 15
      src/components/Table/src/components/TableHeader.vue
  25. 27
      src/components/Table/src/components/TableImg.vue
  26. 5
      src/components/Table/src/components/TableTitle.vue
  27. 63
      src/components/Table/src/components/editable/EditableCell.vue
  28. 42
      src/components/Table/src/components/settings/ColumnSetting.vue
  29. 7
      src/components/Table/src/components/settings/index.vue
  30. 9
      src/components/Table/src/const.ts
  31. 5
      src/components/Table/src/hooks/useColumns.ts
  32. 15
      src/components/Table/src/hooks/useCustomRow.ts
  33. 54
      src/components/Table/src/hooks/useDataSource.ts
  34. 3
      src/components/Table/src/hooks/usePagination.tsx
  35. 10
      src/components/Table/src/hooks/useScrollTo.ts
  36. 15
      src/components/Table/src/hooks/useTable.ts
  37. 6
      src/components/Table/src/hooks/useTableExpand.ts
  38. 8
      src/components/Table/src/hooks/useTableFooter.ts
  39. 6
      src/components/Table/src/hooks/useTableForm.ts
  40. 6
      src/components/Table/src/hooks/useTableHeader.ts
  41. 4
      src/components/Table/src/hooks/useTableScroll.ts
  42. 20
      src/components/Table/src/props.ts
  43. 13
      src/components/Table/src/types/column.ts
  44. 8
      src/components/Table/src/types/pagination.ts
  45. 43
      src/components/Table/src/types/table.ts
  46. 13
      src/hooks/core/useAttrs.ts
  47. 22
      src/hooks/core/useRefs.ts
  48. 16
      src/hooks/event/useScrollTo.ts
  49. 3
      src/hooks/event/useWindowSizeFn.ts
  50. 20
      src/utils/types.ts
  51. 5
      src/views/infra/codegen/components/data.ts

2
src/components/Application/src/search/AppSearchModal.vue

@ -21,7 +21,7 @@ const inputRef = ref<Nullable<HTMLElement>>(null)
const { t } = useI18n()
const { prefixCls } = useDesign('app-search-modal')
const [refs, setRefs] = useRefs()
const { refs, setRefs } = useRefs()
const { getIsMobile } = useAppInject()
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = useMenuSearch(refs, scrollWrap, emit)

65
src/components/Form/src/BasicForm.vue

@ -11,18 +11,17 @@ import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue'
import { dateItemType } from './helper'
import { useFormValues } from './hooks/useFormValues'
import useAdvanced from './hooks/useAdvanced'
import { useFormEvents } from './hooks/useFormEvents'
import { createFormContext } from './hooks/useFormContext'
import { useAutoFocus } from './hooks/useAutoFocus'
import { basicProps } from './props'
import { dateUtil } from '@/utils/dateUtil'
import { deepMerge } from '@/utils'
import { useModalContext } from '@/components/Modal'
import { deepMerge } from '@/utils'
import { dateUtil } from '@/utils/dateUtil'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'BasicForm' })
@ -30,7 +29,7 @@ defineOptions({ name: 'BasicForm' })
const props = defineProps(basicProps)
const emit = defineEmits(['advanced-change', 'reset', 'submit', 'register', 'field-value-change'])
const attrs = useAttrs()
const formModel = reactive<Recordable>({})
const formModel = reactive({})
const modalFn = useModalContext()
const advanceState = reactive<AdvanceState>({
@ -40,11 +39,11 @@ const advanceState = reactive<AdvanceState>({
actionSpan: 6,
})
const defaultValueRef = ref<Recordable>({})
const defaultValueRef = ref({})
const isInitedDefaultRef = ref(false)
const propsRef = ref<Partial<FormProps>>()
const schemaRef = ref<Nullable<FormSchema[]>>(null)
const formElRef = ref<Nullable<FormActionType>>(null)
const schemaRef = ref<FormSchema[] | null>(null)
const formElRef = ref<FormActionType | null>(null)
const { prefixCls } = useDesign('basic-form')
@ -63,7 +62,7 @@ const getFormClass = computed(() => {
})
// Get uniform row style and Row configuration for the entire form
const getRow = computed((): Recordable => {
const getRow = computed(() => {
const { baseRowStyle = {}, rowProps } = unref(getProps)
return {
style: baseRowStyle,
@ -85,10 +84,14 @@ const getSchema = computed((): FormSchema[] => {
isHandleDateDefaultValue = true,
} = schema
// eslint-disable-next-line dot-notation
const valueFormat = componentProps ? componentProps['valueFormat'] : null
const valueFormat = componentProps ? componentProps.valueFormat : null
// handle date type
if (isHandleDateDefaultValue && defaultValue && component && dateItemType.includes(component)) {
if (
isHandleDateDefaultValue
&& defaultValue
&& component
&& dateItemType.includes(component)
) {
if (!Array.isArray(defaultValue)) {
schema.defaultValue = valueFormat
? dateUtil(defaultValue).format(valueFormat)
@ -97,16 +100,20 @@ const getSchema = computed((): FormSchema[] => {
else {
const def: any[] = []
defaultValue.forEach((item) => {
def.push(dateUtil(item))
def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item))
})
schema.defaultValue = def
}
}
}
if (unref(getProps).showAdvancedButton)
return cloneDeep(schemas.filter(schema => schema.component !== 'Divider') as FormSchema[])
else
if (unref(getProps).showAdvancedButton) {
return cloneDeep(
schemas.filter(schema => schema.component !== 'Divider') as FormSchema[],
)
}
else {
return cloneDeep(schemas as FormSchema[])
}
})
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
@ -215,7 +222,7 @@ function setFormModel(key: string, value: any, schema: FormSchema) {
emit('field-value-change', key, value)
// TODO autoLink=false
if (schema && schema.itemProps && !schema.itemProps.autoLink)
validateFields([key]).catch((_) => { })
validateFields([key]).catch((_) => {})
}
function handleEnterPress(e: KeyboardEvent) {
@ -254,14 +261,25 @@ const getFormActionBindProps = computed(() => ({ ...getProps.value, ...advanceSt
</script>
<template>
<Form v-bind="getBindValue" ref="formElRef" :class="getFormClass" :model="formModel" @keypress.enter="handleEnterPress">
<Form
v-bind="getBindValue"
ref="formElRef"
:class="getFormClass"
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Row v-bind="getRow">
<slot name="formHeader" />
<template v-for="schema in getSchema" :key="schema.field">
<FormItem
:is-advanced="fieldsIsAdvancedMap[schema.field]" :table-action="tableAction"
:form-action-type="formActionType as any" :schema="schema" :form-props="getProps"
:all-default-values="defaultValueRef" :form-model="formModel" :set-form-model="setFormModel"
:is-advanced="fieldsIsAdvancedMap[schema.field]"
:table-action="tableAction"
:form-action-type="formActionType"
:schema="schema"
:form-props="getProps"
:all-default-values="defaultValueRef"
:form-model="formModel"
:set-form-model="setFormModel"
>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}" />
@ -270,7 +288,10 @@ const getFormActionBindProps = computed(() => ({ ...getProps.value, ...advanceSt
</template>
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
<template v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']" #[item]="data">
<template
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
#[item]="data"
>
<slot :name="item" v-bind="data || {}" />
</template>
</FormAction>

51
src/components/Form/src/components/ApiCascader.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { ref, unref, watch, watchEffect } from 'vue'
import { Cascader } from 'ant-design-vue'
import { get, omit } from 'lodash-es'
@ -8,12 +9,22 @@ import { isFunction } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useI18n } from '@/hooks/web/useI18n'
interface Option {
value: string
label: string
loading?: boolean
isLeaf?: boolean
children?: Option[]
}
defineOptions({ name: 'ApiCascader' })
const props = defineProps({
value: propTypes.array.def([]),
value: {
type: Array,
},
api: {
type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>,
type: Function as PropType<(arg?: Recordable<any>) => Promise<Option[]>>,
default: null,
},
numberToString: propTypes.bool.def(false),
@ -25,38 +36,28 @@ const props = defineProps({
immediate: propTypes.bool.def(true),
// init fetch params
initFetchParams: {
type: Object as PropType<Recordable>,
type: Object as PropType<Recordable<any>>,
default: () => ({}),
},
//
isLeaf: {
type: Function as PropType<(arg: Recordable) => boolean>,
type: Function as PropType<(arg: Recordable<any>) => boolean>,
default: null,
},
displayRenderArray: {
type: Array,
},
alwaysLoad: propTypes.bool.def(true),
})
const emit = defineEmits(['change', 'defaultChange'])
interface Option {
value: string
label: string
loading?: boolean
isLeaf?: boolean
children?: Option[]
}
const apiData = ref<any[]>([])
const options = ref<Option[]>([])
const loading = ref<boolean>(false)
const emitData = ref<any[]>([])
const isFirstLoad = ref(false)
const isFirstLoad = ref(true)
const { t } = useI18n()
// Embedded in the form, just use the hook binding to perform form verification
const [state]: any = useRuleFormItem(props, 'value', 'change', emitData)
const [state]: any[] = useRuleFormItem(props, 'value', 'change', emitData)
watch(
apiData,
@ -69,7 +70,7 @@ watch(
function generatorOptions(options: any[]): Option[] {
const { labelField, valueField, numberToString, childrenField, isLeaf } = props
return options.reduce((prev, next: Recordable) => {
return options.reduce((prev, next: Recordable<any>) => {
if (next) {
const value = next[valueField]
const item = {
@ -147,17 +148,13 @@ watchEffect(() => {
watch(
() => props.initFetchParams,
() => {
if (props.alwaysLoad)
initialFetch()
else
!unref(isFirstLoad) && initialFetch()
!unref(isFirstLoad) && initialFetch()
},
{ deep: true },
)
function handleChange(keys, args) {
emitData.value = keys
emitData.value = args
emit('defaultChange', keys, args)
}
@ -174,8 +171,12 @@ function handleRenderDisplay({ labels, selectedOptions }) {
<template>
<Cascader
v-model:value="state" :options="options" :load-data="loadData" change-on-select
:display-render="handleRenderDisplay" @change="handleChange"
v-model:value="state"
:options="options"
:load-data="loadData"
change-on-select
:display-render="handleRenderDisplay"
@change="handleChange"
>
<template v-if="loading" #suffixIcon>
<LoadingOutlined spin />

42
src/components/Form/src/components/ApiRadioGroup.vue

@ -2,6 +2,7 @@
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
-->
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref, unref, watch, watchEffect } from 'vue'
import { Radio } from 'ant-design-vue'
import { get, omit } from 'lodash-es'
@ -10,47 +11,49 @@ import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useAttrs } from '@/hooks/core/useAttrs'
import { propTypes } from '@/utils/propTypes'
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean }
defineOptions({ name: 'ApiRadioGroup' })
const props = defineProps({
api: {
type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
type: Function as PropType<(arg?: any | string) => Promise<OptionsItem[]>>,
default: null,
},
params: {
type: [Object, String] as PropType<Recordable | string>,
type: [Object, String] as PropType<any | string>,
default: () => ({}),
},
value: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
},
isBtn: propTypes.bool.def(false),
isBtn: {
type: [Boolean] as PropType<boolean>,
default: false,
},
numberToString: propTypes.bool,
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(true),
})
const emit = defineEmits(['optionsChange', 'change'])
const emit = defineEmits(['options-change', 'change'])
const RadioButton = Radio.Button
const RadioGroup = Radio.Group
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean }
const options = ref<OptionsItem[]>([])
const loading = ref(false)
const isFirstLoad = ref(true)
const emitData = ref<any[]>([])
const attrs = useAttrs()
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props)
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
// Processing options value
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props
return unref(options).reduce((prev, next: Recordable) => {
return unref(options).reduce((prev, next: any) => {
if (next) {
const value = next[valueField]
prev.push({
@ -70,11 +73,7 @@ watchEffect(() => {
watch(
() => props.params,
() => {
if (props.alwaysLoad)
fetch()
else
!unref(isFirstLoad) && fetch()
!unref(isFirstLoad) && fetch()
},
{ deep: true },
)
@ -106,21 +105,26 @@ async function fetch() {
}
function emitChange() {
emit('optionsChange', unref(getOptions))
emit('options-change', unref(getOptions))
}
function handleChange(args) {
function handleClick(...args) {
emitData.value = args
}
</script>
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
<RadioButton
v-if="props.isBtn"
:value="item.value"
:disabled="item.disabled"
@click="handleClick(item)"
>
{{ item.label }}
</RadioButton>
<Radio v-else :value="item.value" :disabled="item.disabled">
<Radio v-else :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
{{ item.label }}
</Radio>
</template>

44
src/components/Form/src/components/ApiSelect.vue

@ -1,49 +1,46 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref, unref, watch } from 'vue'
import { Select } from 'ant-design-vue'
import type { SelectValue } from 'ant-design-vue/es/select'
import { get, omit } from 'lodash-es'
import { LoadingOutlined } from '@ant-design/icons-vue'
import type { SelectValue } from 'ant-design-vue/lib/select'
import { isFunction } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes'
interface OptionsItem { label: string; value: string; disabled?: boolean }
defineOptions({ name: 'ApiSelect', inheritAttrs: false })
const props = defineProps({
value: { type: [Array, Object, String, Number] as PropType<SelectValue> },
numberToString: propTypes.bool,
api: {
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>,
default: null,
},
// api params
params: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
params: propTypes.any.def({}),
// support xxx.xxx.xx
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false),
options: {
type: Array<OptionsItem>,
default: [],
},
})
const emit = defineEmits(['optionsChange', 'change', 'update:value'])
interface OptionsItem { label: string; value: string; disabled?: boolean }
const emit = defineEmits(['options-change', 'change', 'update:value'])
const options = ref<OptionsItem[]>([])
const loading = ref(false)
//
const isFirstLoaded = ref(false)
const emitData = ref<OptionsItem[]>([])
const { t } = useI18n()
// Embedded in the form, just use the hook binding to perform form verification
@ -52,7 +49,7 @@ const [state] = useRuleFormItem(props, 'value', 'change', emitData)
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props
return unref(options).reduce((prev, next: Recordable) => {
const data = unref(options).reduce((prev, next: any) => {
if (next) {
const value = get(next, valueField)
prev.push({
@ -63,6 +60,7 @@ const getOptions = computed(() => {
}
return prev
}, [] as OptionsItem[])
return data.length > 0 ? data : props.options
})
watch(
@ -75,11 +73,7 @@ watch(
watch(
() => props.params,
() => {
if (props.alwaysLoad)
fetch()
else
!unref(isFirstLoaded) && fetch()
!unref(isFirstLoaded) && fetch()
},
{ deep: true, immediate: props.immediate },
)
@ -113,18 +107,15 @@ async function fetch() {
async function handleFetch(open: boolean) {
if (open) {
if (props.alwaysLoad) {
if (props.alwaysLoad)
await fetch()
}
else if (!props.immediate && !unref(isFirstLoaded)) {
else if (!props.immediate && !unref(isFirstLoaded))
await fetch()
isFirstLoaded.value = false
}
}
}
function emitChange() {
emit('optionsChange', unref(getOptions))
emit('options-change', unref(getOptions))
}
function handleChange(_, ...args) {
@ -134,7 +125,10 @@ function handleChange(_, ...args) {
<template>
<Select
v-bind="$attrs" v-model:value="state" :options="getOptions" @dropdown-visible-change="handleFetch"
v-bind="$attrs"
v-model:value="state"
:options="getOptions"
@dropdown-visible-change="handleFetch"
@change="handleChange"
>
<template v-for="item in Object.keys($slots)" #[item]="data">

44
src/components/Form/src/components/ApiTransfer.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed, ref, unref, useAttrs, watch, watchEffect } from 'vue'
import type { PropType } from 'vue'
import { computed, ref, unref, watch, watchEffect } from 'vue'
import { Transfer } from 'ant-design-vue'
import { get, omit } from 'lodash-es'
import type { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
@ -9,16 +10,16 @@ import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'ApiTransfer' })
const props = defineProps({
value: { type: Array as PropType<Array<any>> },
value: { type: Array as PropType<Array<string>> },
api: {
type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>,
type: Function as PropType<(arg) => Promise<TransferItem[]>>,
default: null,
},
params: { type: Object },
dataSource: { type: Array as PropType<Array<TransferItem>> },
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(true),
afterFetch: { type: Function as PropType<Fn> },
alwaysLoad: propTypes.bool.def(false),
afterFetch: { type: Function },
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('key'),
@ -31,20 +32,22 @@ const props = defineProps({
showSelectAll: { type: Boolean, default: true },
targetKeys: { type: Array as PropType<Array<string>> },
})
const emit = defineEmits(['optionsChange', 'change'])
const attrs = useAttrs()
const emit = defineEmits(['options-change', 'change'])
// const attrs = useAttrs()
const _dataSource = ref<TransferItem[]>([])
const _targetKeys = ref<string[]>([])
const getAttrs = computed(() => {
return {
...(!props.api ? { dataSource: unref(_dataSource) } : {}),
...attrs,
}
})
// const getAttrs = computed(() => {
// return {
// ...(!props.api ? { dataSource: unref(_dataSource) } : {}),
// ...attrs,
// }
// })
const getdataSource = computed(() => {
const { labelField, valueField } = props
return unref(_dataSource).reduce((prev, next: Recordable) => {
return unref(_dataSource).reduce((prev, next) => {
if (next) {
prev.push({
...omit(next, [labelField, valueField]),
@ -56,9 +59,9 @@ const getdataSource = computed(() => {
}, [] as TransferItem[])
})
const getTargetKeys = computed<string[]>(() => {
// if (unref(_targetKeys).length > 0)
// return unref(_targetKeys)
/* if (unref(_targetKeys).length > 0) {
return unref(_targetKeys);
} */
if (Array.isArray(props.value))
return props.value
@ -67,15 +70,18 @@ const getTargetKeys = computed<string[]>(() => {
return []
})
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
_targetKeys.value = keys
console.log(direction)
console.log(moveKeys)
emit('change', keys)
}
watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch()
})
watch(
() => props.params,
() => {
@ -83,6 +89,7 @@ watch(
},
{ deep: true },
)
async function fetch() {
const api = props.api
if (!api || !isFunction(api)) {
@ -109,13 +116,12 @@ async function fetch() {
}
}
function emitChange() {
emit('optionsChange', unref(getdataSource))
emit('options-change', unref(getdataSource))
}
</script>
<template>
<Transfer
v-bind="getAttrs"
:data-source="getdataSource"
:filter-option="filterOption"
:render="(item) => item.title"

30
src/components/Form/src/components/ApiTree.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, onMounted, ref, unref, useAttrs, useSlots, watch } from 'vue'
import { computed, onMounted, ref, unref, useAttrs, watch } from 'vue'
import type { TreeProps } from 'ant-design-vue'
import { Tree } from 'ant-design-vue'
import { get } from 'lodash-es'
@ -7,30 +7,30 @@ import type { DataNode } from 'ant-design-vue/es/tree'
import { isArray, isFunction } from '@/utils/is'
import { handleTree as handleTreeFn } from '@/utils/tree'
import { propTypes } from '@/utils/propTypes'
import type { AnyFunction, Recordable } from '@/utils/types'
import { useRuleFormItem } from '@/hooks/component/useFormItem'
defineOptions({ name: 'ApiTree' })
const props = defineProps({
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
params: { type: Object },
immediate: propTypes.bool.def(true),
resultField: propTypes.string.def(''),
afterFetch: { type: Function as PropType<Fn> },
afterFetch: { type: Function as PropType<AnyFunction> },
handleTree: propTypes.string.def(''),
alwaysLoad: propTypes.bool.def(true),
value: {
type: Array as PropType<TreeProps['selectedKeys']>,
},
})
const emit = defineEmits(['optionsChange', 'change', 'update:value'])
const emit = defineEmits(['options-change', 'change', 'update:value'])
const attrs = useAttrs()
const slots = useSlots()
const treeData = ref<DataNode[]>([])
const isFirstLoaded = ref<boolean>(false)
const loading = ref(false)
const emitData = ref<any[]>([])
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
const getAttrs = computed(() => {
@ -50,11 +50,7 @@ watch(
watch(
() => props.params,
() => {
if (props.alwaysLoad)
fetch()
else
!unref(isFirstLoaded) && fetch()
!unref(isFirstLoaded) && fetch()
},
{ deep: true },
)
@ -62,11 +58,7 @@ watch(
watch(
() => props.immediate,
(v) => {
if (props.alwaysLoad)
v && fetch()
else
v && !isFirstLoaded.value && fetch()
v && !isFirstLoaded.value && fetch()
},
)
@ -101,13 +93,13 @@ async function fetch() {
treeData.value = (result as (Recordable & { key: string | number })[]) || []
isFirstLoaded.value = true
emit('optionsChange', treeData.value)
emit('options-change', treeData.value)
}
</script>
<template>
<Tree v-bind="getAttrs" v-model:selected-keys="state">
<template v-for="item in Object.keys(slots)" #[item]="data">
<Tree v-bind="getAttrs" v-model:selectedKeys="state">
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}" />
</template>
</Tree>

41
src/components/Form/src/components/ApiTreeSelect.vue

@ -5,12 +5,13 @@ import { get, set } from 'lodash-es'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { isArray, isFunction } from '@/utils/is'
import { propTypes } from '@/utils/propTypes'
import type { Recordable } from '@/utils/types'
import { handleTree as handleTreeFn } from '@/utils/tree'
defineOptions({ name: 'ApiTreeSelect' })
const props = defineProps({
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
params: { type: Object },
immediate: propTypes.bool.def(true),
async: propTypes.bool.def(false),
@ -19,12 +20,13 @@ const props = defineProps({
parentId: propTypes.number.def(0),
parentLabel: propTypes.string.def(''),
parentFiled: propTypes.string.def('name'),
alwaysLoad: propTypes.bool.def(true),
labelField: propTypes.string.def('name'),
valueField: propTypes.string.def('id'),
childrenField: propTypes.string.def('children'),
})
const emit = defineEmits(['optionsChange', 'change', 'load-data'])
const emit = defineEmits(['options-change', 'change', 'load-data'])
const attrs = useAttrs()
const treeData = ref<Recordable[]>([])
const treeData = ref<Recordable<any>[]>([])
const isFirstLoaded = ref<boolean>(false)
const loading = ref(false)
const getAttrs = computed(() => {
@ -33,6 +35,11 @@ const getAttrs = computed(() => {
...attrs,
}
})
const fieldNames = {
children: props.childrenField,
value: props.valueField,
label: props.labelField,
}
function handleChange(...args) {
emit('change', ...args)
@ -41,11 +48,7 @@ function handleChange(...args) {
watch(
() => props.params,
() => {
if (props.alwaysLoad)
fetch()
else
!unref(isFirstLoaded) && fetch()
!unref(isFirstLoaded) && fetch()
},
{ deep: true },
)
@ -53,11 +56,7 @@ watch(
watch(
() => props.immediate,
(v) => {
if (props.alwaysLoad)
v && fetch()
else
v && !isFirstLoaded.value && fetch()
v && !isFirstLoaded.value && fetch()
},
)
@ -77,7 +76,7 @@ function onLoadData(treeNode) {
async function fetch() {
const { api } = props
if (!api || !isFunction(api))
if (!api || !isFunction(api) || loading.value)
return
loading.value = true
treeData.value = []
@ -106,14 +105,18 @@ async function fetch() {
else {
treeData.value = (result as Recordable[]) || []
}
isFirstLoaded.value = true
emit('optionsChange', treeData.value)
emit('options-change', treeData.value)
}
</script>
<template>
<TreeSelect v-bind="getAttrs" :load-data="async ? onLoadData : undefined" @change="handleChange">
<TreeSelect
v-bind="getAttrs"
:field-names="fieldNames"
:load-data="async ? onLoadData : undefined"
@change="handleChange"
>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}" />
</template>

38
src/components/Form/src/components/FormAction.vue

@ -1,8 +1,8 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed } from 'vue'
import { Col, Form } from 'ant-design-vue'
import type { ColEx } from '../types'
import { useFormContext } from '../hooks/useFormContext'
import type { ButtonProps } from '@/components/Button'
import { Button } from '@/components/Button'
@ -10,6 +10,8 @@ import { BasicArrow } from '@/components/Basic'
import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes'
type ButtonOptions = Partial<ButtonProps> & { text: string }
defineOptions({ name: 'BasicFormAction' })
const props = defineProps({
@ -34,12 +36,11 @@ const props = defineProps({
hideAdvanceBtn: propTypes.bool,
})
const emit = defineEmits(['toggleAdvanced'])
const FormItem = Form.Item
const emit = defineEmits(['toggle-advanced'])
type ButtonOptions = Partial<ButtonProps> & { text: string }
const { resetAction, submitAction } = useFormContext()
const FormItem = Form.Item
const { t } = useI18n()
const actionColOpt = computed(() => {
@ -74,10 +75,8 @@ const getSubmitBtnOptions = computed(() => {
})
function toggleAdvanced() {
emit('toggleAdvanced')
emit('toggle-advanced')
}
const { resetAction, submitAction } = useFormContext()
</script>
<template>
@ -85,17 +84,34 @@ const { resetAction, submitAction } = useFormContext()
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
<FormItem>
<slot name="resetBefore" />
<Button v-if="showResetButton" type="default" class="mr-2" v-bind="getResetBtnOptions" @click="resetAction">
<Button
v-if="showResetButton"
type="default"
class="mr-2"
v-bind="getResetBtnOptions"
@click="resetAction"
>
{{ getResetBtnOptions.text }}
</Button>
<slot name="submitBefore" />
<Button v-if="showSubmitButton" type="primary" class="mr-2" v-bind="getSubmitBtnOptions" @click="submitAction">
<Button
v-if="showSubmitButton"
type="primary"
class="mr-2"
v-bind="getSubmitBtnOptions"
@click="submitAction"
>
{{ getSubmitBtnOptions.text }}
</Button>
<slot name="advanceBefore" />
<Button v-if="showAdvancedButton && !hideAdvanceBtn" type="link" size="small" @click="toggleAdvanced">
<Button
v-if="showAdvancedButton && !hideAdvanceBtn"
type="link"
size="small"
@click="toggleAdvanced"
>
{{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
</Button>

100
src/components/Form/src/components/FormItem.vue

@ -1,7 +1,7 @@
<script lang="tsx">
import type { Ref } from 'vue'
import type { PropType, Ref } from 'vue'
import { computed, defineComponent, toRefs, unref } from 'vue'
import type { Rule } from 'ant-design-vue/lib/form'
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form'
import { Col, Divider, Form } from 'ant-design-vue'
import { cloneDeep, upperFirst } from 'lodash-es'
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from '../types/form'
@ -13,6 +13,7 @@ import type { TableActionType } from '@/components/Table'
import { BasicHelp } from '@/components/Basic'
import { isBoolean, isFunction, isNull } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper'
import type { Nullable, Recordable } from '@/utils/types'
import { useI18n } from '@/hooks/web/useI18n'
export default defineComponent({
@ -28,11 +29,11 @@ export default defineComponent({
default: () => ({}),
},
allDefaultValues: {
type: Object as PropType<Recordable>,
type: Object as PropType<Recordable<any>>,
default: () => ({}),
},
formModel: {
type: Object as PropType<Recordable>,
type: Object as PropType<Recordable<any>>,
default: () => ({}),
},
setFormModel: {
@ -69,7 +70,7 @@ export default defineComponent({
...mergeDynamicData,
...allDefaultValues,
...formModel,
} as Recordable,
} as Recordable<any>,
schema,
}
})
@ -90,7 +91,7 @@ export default defineComponent({
componentProps,
)
}
return componentProps as Recordable
return componentProps as Recordable<any>
})
const getDisable = computed(() => {
@ -110,7 +111,11 @@ export default defineComponent({
function getShow(): { isShow: boolean; isIfShow: boolean } {
const { show, ifShow } = props.schema
const { showAdvancedButton } = props.formProps
const itemIsAdvanced = showAdvancedButton ? (isBoolean(props.isAdvanced) ? props.isAdvanced : true) : true
const itemIsAdvanced = showAdvancedButton
? isBoolean(props.isAdvanced)
? props.isAdvanced
: true
: true
let isShow = true
let isIfShow = true
@ -131,18 +136,29 @@ export default defineComponent({
return { isShow, isIfShow }
}
function handleRules(): Rule[] {
const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required } = props.schema
function handleRules(): ValidationRule[] {
const {
rules: defRules = [],
component,
rulesMessageJoinLabel,
label,
dynamicRules,
required,
} = props.schema
if (isFunction(dynamicRules))
return dynamicRules(unref(getValues)) as Rule[]
return dynamicRules(unref(getValues)) as ValidationRule[]
let rules: Rule[] = cloneDeep(defRules) as Rule[]
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') ? rulesMessageJoinLabel : globalRulesMessageJoinLabel
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel
: globalRulesMessageJoinLabel
const assertLabel = joinLabel ? label : ''
const defaultMsg = component ? createPlaceholderMessage(component) + assertLabel : assertLabel
const defaultMsg = component
? createPlaceholderMessage(component) + assertLabel
: assertLabel
function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg
@ -192,7 +208,9 @@ export default defineComponent({
}
}
const requiredRuleIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'))
const requiredRuleIndex: number = rules.findIndex(
rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
)
if (requiredRuleIndex !== -1) {
const rule = rules[requiredRuleIndex]
@ -213,9 +231,11 @@ export default defineComponent({
// Maximum input length rule check
const characterInx = rules.findIndex(val => val.max)
if (characterInx !== -1 && !rules[characterInx].validator)
rules[characterInx].message = rules[characterInx].message || t('component.form.maxTip', [rules[characterInx].max] as Recordable)
if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message
= rules[characterInx].message
|| t('component.form.maxTip', [rules[characterInx].max] as Recordable<any>)
}
return rules
}
@ -223,24 +243,29 @@ export default defineComponent({
if (!isComponentFormSchema(props.schema))
return null
const { renderComponentContent, component, field, changeEvent = 'change', valueField } = props.schema
const {
renderComponentContent,
component,
field,
changeEvent = 'change',
valueField,
} = props.schema
const isCheck = component && ['Switch', 'Checkbox'].includes(component)
const eventKey = `on${upperFirst(changeEvent)}`
const { autoSetPlaceHolder, size } = props.formProps
const propsData: Recordable = {
const propsData: Recordable<any> = {
allowClear: true,
getPopupContainer: (trigger: Element) => trigger.parentNode!.parentNode,
getPopupContainer: (trigger: Element) => trigger.parentNode,
size,
...unref(getComponentsProps),
disabled: unref(getDisable),
}
const on = {
[eventKey]: (...args: Nullable<Recordable>[]) => {
[eventKey]: (...args: Nullable<Recordable<any>>[]) => {
const [e] = args
if (propsData[eventKey])
propsData[eventKey](...args)
@ -254,17 +279,18 @@ export default defineComponent({
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder
// RangePicker place is an array
if (isCreatePlaceholder && component !== 'RangePicker' && component)
propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
propsData.placeholder
= unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
}
propsData.codeField = field
propsData.formValues = unref(getValues)
const bindValue: Recordable = {
const bindValue: Recordable<any> = {
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
}
const compAttr: Recordable = {
const compAttr: Recordable<any> = {
...propsData,
...on,
...bindValue,
@ -294,7 +320,9 @@ export default defineComponent({
: (
label
)
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage
const getHelpMessage = isFunction(helpMessage)
? helpMessage(unref(getValues))
: helpMessage
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0))
return renderLabel
@ -310,9 +338,7 @@ export default defineComponent({
const { itemProps, slot, render, field, suffix, component } = props.schema
const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
const { colon } = props.formProps
const opts = { disabled: unref(getDisable) }
if (component === 'Divider') {
return (
<Col span={24}>
@ -322,7 +348,11 @@ export default defineComponent({
}
else {
const getContent = () => {
return slot ? getSlot(slots, slot, unref(getValues), opts) : render ? render(unref(getValues), opts) : renderComponent()
return slot
? getSlot(slots, slot, unref(getValues), opts)
: render
? render(unref(getValues), opts)
: renderComponent()
}
const showSuffix = !!suffix
@ -342,7 +372,7 @@ export default defineComponent({
name={field}
colon={colon}
class={{ 'suffix-item': showSuffix }}
{...(itemProps as Recordable)}
{...(itemProps as Recordable<any>)}
label={renderLabelHelpMessage()}
rules={handleRules()}
labelCol={labelCol}
@ -369,7 +399,11 @@ export default defineComponent({
const opts = { disabled: unref(getDisable) }
const getContent = () => {
return colSlot ? getSlot(slots, colSlot, values, opts) : renderColContent ? renderColContent(values, opts) : renderItem()
return colSlot
? getSlot(slots, colSlot, values, opts)
: renderColContent
? renderColContent(values, opts)
: renderItem()
}
return (

18
src/components/Form/src/components/RadioButtonGroup.vue

@ -2,12 +2,16 @@
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
-->
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref } from 'vue'
import { Radio } from 'ant-design-vue'
import { isString } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useAttrs } from '@/hooks/core/useAttrs'
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean }
type RadioItem = string | OptionsItem
defineOptions({ name: 'RadioButtonGroup' })
const props = defineProps({
value: {
@ -18,15 +22,12 @@ const props = defineProps({
default: () => [],
},
})
const emits = defineEmits(['change'])
const RadioButton = Radio.Button
const RadioGroup = Radio.Group
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean }
type RadioItem = string | OptionsItem
// const emits = defineEmits(['change'])
const RadioButton = Radio.Button
const RadioGroup = Radio.Group
const attrs = useAttrs()
const emitData = ref<any[]>([])
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
@ -44,16 +45,15 @@ const getOptions = computed((): OptionsItem[] => {
return options.map(item => ({ label: item, value: item })) as OptionsItem[]
})
function handleClick(args) {
function handleClick(...args) {
emitData.value = args
emits('change', emitData.value)
}
</script>
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton :value="item.value" :disabled="item.disabled" @click="handleClick(item.value)">
<RadioButton :value="item.value" :disabled="item.disabled" @click="handleClick(item)">
{{ item.label }}
</RadioButton>
</template>

14
src/components/Form/src/helper.ts

@ -1,4 +1,4 @@
import type { Rule } from 'ant-design-vue/lib/form'
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form'
import type { ComponentType } from './types'
import { useI18n } from '@/hooks/web/useI18n'
import { dateUtil } from '@/utils/dateUtil'
@ -35,14 +35,20 @@ function genType() {
return [...DATE_TYPE, 'RangePicker']
}
export function setComponentRuleType(rule: Rule, component: ComponentType, valueFormat: string) {
export function setComponentRuleType(
rule: ValidationRule,
component: ComponentType,
valueFormat: string,
) {
if (Reflect.has(rule, 'type'))
return
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component))
rule.type = valueFormat ? 'string' : 'object'
else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component))
rule.type = 'array'
else if (['InputNumber'].includes(component))
rule.type = 'number'
}
@ -51,6 +57,7 @@ export function processDateValue(attr: Recordable, component: string) {
const { valueFormat, value } = attr
if (valueFormat)
attr.value = isObject(value) ? dateUtil(value as unknown as Date).format(valueFormat) : value
else if (DATE_TYPE.includes(component) && value)
attr.value = dateUtil(attr.value)
}
@ -74,7 +81,6 @@ export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch',
// TODO 自定义组件封装会出现验证问题,因此这里目前改成手动触发验证
export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'Upload',
'ApiSelect',
'ApiTransfer',
'ApiTree',
'ApiTreeSelect',
@ -82,4 +88,6 @@ export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'ApiCascader',
'AutoComplete',
'RadioButtonGroup',
'ImageUpload',
'ApiSelect',
]

13
src/components/Form/src/hooks/useAdvanced.ts

@ -70,10 +70,13 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
const xxlWidth = Number.parseInt(itemCol.xxl as string) || xlWidth
if (width <= screenEnum.LG)
itemColSum += mdWidth
else if (width < screenEnum.XL)
itemColSum += lgWidth
else if (width < screenEnum.XXL)
itemColSum += xlWidth
else
itemColSum += xxlWidth
@ -84,7 +87,10 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
advanceState.hideAdvanceBtn = true
advanceState.isAdvanced = true
}
else if (itemColSum > BASIC_COL_LEN * 2 && itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)) {
else if (
itemColSum > BASIC_COL_LEN * 2
&& itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
) {
advanceState.hideAdvanceBtn = false
// More than 3 lines collapsed by default
@ -131,7 +137,10 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
}
if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced({ ...baseColProps, ...colProps }, itemColSum)
const { itemColSum: sum, isAdvanced } = getAdvanced(
{ ...baseColProps, ...colProps },
itemColSum,
)
itemColSum = sum || 0
if (isAdvanced)

7
src/components/Form/src/hooks/useAutoFocus.ts

@ -8,7 +8,12 @@ interface UseAutoFocusContext {
isInitedDefault: Ref<boolean>
formElRef: Ref<FormActionType>
}
export function useAutoFocus({ getSchema, getProps, formElRef, isInitedDefault }: UseAutoFocusContext) {
export async function useAutoFocus({
getSchema,
getProps,
formElRef,
isInitedDefault,
}: UseAutoFocusContext) {
watchEffect(async () => {
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem)
return

10
src/components/Form/src/hooks/useForm.ts

@ -86,12 +86,16 @@ export function useForm(props?: Props): UseFormReturnType {
return unref(formRef)?.getFieldsValue() as T
},
setFieldsValue: async (values: Recordable) => {
setFieldsValue: async <T extends Recordable<any>>(values: T) => {
const form = await getForm()
form.setFieldsValue(values)
},
appendSchemaByField: async (schema: FormSchema | FormSchema[], prefixField: string | undefined, first?: boolean) => {
appendSchemaByField: async (
schema: FormSchema | FormSchema[],
prefixField: string | undefined,
first?: boolean,
) => {
const form = await getForm()
form.appendSchemaByField(schema, prefixField, first)
},
@ -101,7 +105,7 @@ export function useForm(props?: Props): UseFormReturnType {
return form.submit()
},
validate: async <T = any>(nameList?: NamePath[] | false): Promise<T> => {
validate: async <T = Recordable>(nameList?: NamePath[] | false): Promise<T> => {
const form = await getForm()
return form.validate(nameList)
},

18
src/components/Form/src/hooks/useFormEvents.ts

@ -91,7 +91,7 @@ export function useFormEvents({
if (fieldKeys.length) {
// eslint-disable-next-line array-callback-return
fieldKeys.map((field) => {
formModel[field] = defaultValueObj[field]
formModel[field] = defaultValueObj![field]
})
}
formModel[key] = getDefaultValue(schema, defaultValueRef, key)
@ -127,7 +127,7 @@ export function useFormEvents({
value = handleInputNumberValue(schema?.component, value)
const { componentProps } = schema || {}
let _props = componentProps
let _props = componentProps as any
if (typeof componentProps === 'function')
_props = _props({ formModel: unref(formModel) })
@ -230,18 +230,14 @@ export function useFormEvents({
}
const index = schemaList.findIndex(schema => schema.field === prefixField)
const _schemaList = isObject(schema) ? [schema as FormSchema] : (schema as FormSchema[])
if (!prefixField || index === -1 || first) {
if (!prefixField || index === -1 || first)
first ? schemaList.unshift(..._schemaList) : schemaList.push(..._schemaList)
schemaRef.value = schemaList
_setDefaultValue(schema)
return
}
if (index !== -1)
schemaList.splice(index + 1, 0, ..._schemaList)
_setDefaultValue(schema)
else if (index !== -1)
schemaList.splice(index + 1, 0, ..._schemaList)
schemaRef.value = schemaList
_setDefaultValue(schema)
}
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
@ -262,7 +258,7 @@ export function useFormEvents({
)
return
}
schemaRef.value = updateData
schemaRef.value = updateData as FormSchema[]
}
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {

12
src/components/Form/src/hooks/useFormValues.ts

@ -48,7 +48,12 @@ function tryDeconstructObject(key: string, value: any, target: Recordable) {
}
}
export function useFormValues({ defaultValueRef, getSchema, formModel, getProps }: UseFormValuesContext) {
export function useFormValues({
defaultValueRef,
getSchema,
formModel,
getProps,
}: UseFormValuesContext) {
// Processing form values
function handleFormValues(values: Recordable) {
if (!isObject(values))
@ -73,6 +78,7 @@ export function useFormValues({ defaultValueRef, getSchema, formModel, getProps
// remove params from URL
if (value === '')
value = undefined
else
value = value.trim()
}
@ -138,9 +144,9 @@ export function useFormValues({ defaultValueRef, getSchema, formModel, getProps
if (fieldKeys.length) {
// eslint-disable-next-line array-callback-return
fieldKeys.map((field) => {
obj[field] = defaultValueObj[field]
obj[field] = defaultValueObj![field]
if (formModel[field] === undefined)
formModel[field] = defaultValueObj[field]
formModel[field] = defaultValueObj![field]
})
}
if (!isNullOrUnDef(defaultValue)) {

7
src/components/Form/src/hooks/useLabelWidth.ts

@ -9,7 +9,12 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
const { labelWidth, disabledLabelWidth } = schemaItem
const { labelWidth: globalLabelWidth, labelCol: globalLabelCol, wrapperCol: globWrapperCol, layout } = unref(propsRef)
const {
labelWidth: globalLabelWidth,
labelCol: globalLabelCol,
wrapperCol: globWrapperCol,
layout,
} = unref(propsRef)
// If labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {

4
src/components/Form/src/props.ts

@ -1,4 +1,4 @@
import type { CSSProperties } from 'vue'
import type { CSSProperties, PropType } from 'vue'
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
import type { FieldMapToTime, FormSchema } from './types/form'
@ -41,7 +41,7 @@ export const basicProps = {
autoSubmitOnEnter: propTypes.bool.def(false),
submitOnReset: propTypes.bool,
submitOnChange: propTypes.bool,
size: propTypes.oneOf(['default', 'small', 'large']),
size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
// 禁用表单
disabled: propTypes.bool,
emptySpan: {

44
src/components/Form/src/types/form.ts

@ -33,9 +33,13 @@ export interface FormActionType {
resetSchema: (data: Partial<FormSchemaInner> | Partial<FormSchemaInner>[]) => Promise<void>
setProps: (formProps: Partial<FormProps>) => Promise<void>
removeSchemaByField: (field: string | string[]) => Promise<void>
appendSchemaByField: (schema: FormSchema | FormSchema[], prefixField: string | undefined, first?: boolean | undefined) => Promise<void>
appendSchemaByField: (
schema: FormSchemaInner | FormSchemaInner[],
prefixField: string | undefined,
first?: boolean | undefined,
) => Promise<void>
validateFields: (nameList?: NamePath[]) => Promise<any>
validate: <T = any>(nameList?: NamePath[] | false) => Promise<T>
validate: <T = Recordable>(nameList?: NamePath[] | false) => Promise<T>
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>
}
@ -139,13 +143,25 @@ interface BaseFormSchema {
// Auxiliary text
subLabel?: string
// Help text on the right side of the text
helpMessage?: string | string[] | ((renderCallbackParams: RenderCallbackParams) => string | string[])
helpMessage?:
| string
| string[]
| ((renderCallbackParams: RenderCallbackParams) => string | string[])
// BaseHelp component props
helpComponentProps?: Partial<HelpComponentProps>
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
labelWidth?: string | number
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
disabledLabelWidth?: boolean
// Component parameters
componentProps?:
| ((opt: {
schema: FormSchema
tableAction: TableActionType
formActionType: FormActionType
formModel: Recordable
}) => Recordable)
| object
// Required
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
@ -192,7 +208,11 @@ interface BaseFormSchema {
opts: RenderOpts,
) => VNode | VNode[] | string
renderComponentContent?: ((renderCallbackParams: RenderCallbackParams, opts: RenderOpts) => any) | VNode | VNode[] | string
renderComponentContent?:
| ((renderCallbackParams: RenderCallbackParams, opts: RenderOpts) => any)
| VNode
| VNode[]
| string
// Custom slot, similar to renderColContent
colSlot?: string
@ -201,24 +221,14 @@ interface BaseFormSchema {
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]
}
interface ComponentFormSchema extends BaseFormSchema {
export interface ComponentFormSchema extends BaseFormSchema {
// render component
component: ComponentType
// Component parameters
componentProps?:
| ((opt: {
schema: FormSchema
tableAction: TableActionType
formActionType: FormActionType
formModel: Recordable
}) => Recordable)
| object
}
interface SlotFormSchema extends BaseFormSchema {
export interface SlotFormSchema extends BaseFormSchema {
// Custom slot, in from-item
slot?: string
slot: string
}
export type FormSchema = ComponentFormSchema | SlotFormSchema

65
src/components/Table/src/BasicTable.vue

@ -6,6 +6,7 @@ import { omit } from 'lodash-es'
import type { BasicTableProps, ColumnChangeParam, InnerHandlers, SizeType, TableActionType } from './types/table'
import HeaderCell from './components/HeaderCell.vue'
import { usePagination } from './hooks/usePagination'
import { useColumns } from './hooks/useColumns'
import { useDataSource } from './hooks/useDataSource'
import { useLoading } from './hooks/useLoading'
@ -21,14 +22,16 @@ import { useTableFooter } from './hooks/useTableFooter'
import { useTableForm } from './hooks/useTableForm'
import { basicProps } from './props'
import { useDesign } from '@/hooks/web/useDesign'
import { BasicForm, useForm } from '@/components/Form'
import { PageWrapperFixedHeightKey } from '@/enums/pageEnum'
import { BasicForm, useForm } from '@/components/Form'
import { isFunction } from '@/utils/is'
import { warn } from '@/utils/log'
defineOptions({ name: 'BasicTable' })
const props = defineProps(basicProps)
const emit = defineEmits([
'fetch-success',
'fetch-error',
@ -47,11 +50,11 @@ const emit = defineEmits([
'change',
'columns-change',
])
const slots = useSlots()
const attrs = useAttrs()
const tableElRef = ref(null)
const tableData = ref<Recordable[]>([])
const tableData = ref([])
const wrapRef = ref(null)
const formRef = ref(null)
@ -72,7 +75,13 @@ watchEffect(() => {
})
const { getLoading, setLoading } = useLoading(getProps)
const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } = usePagination(getProps)
const {
getPaginationInfo,
getPagination,
setPagination,
setShowPagination,
getShowPagination,
} = usePagination(getProps)
const {
getRowSelection,
@ -121,10 +130,16 @@ function handleTableChange(pagination: any, filters: any, sorter: any, extra: an
onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra)
}
const { getViewColumns, getColumns, setCacheColumnsByField, setCacheColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getProps,
getPaginationInfo,
)
const {
getViewColumns,
getColumns,
setCacheColumnsByField,
setCacheColumns,
setColumnWidth,
setColumns,
getColumnsRef,
getCacheColumns,
} = useColumns(getProps, getPaginationInfo)
const { getScrollRef, redoHeight } = useTableScroll(
getProps,
@ -148,7 +163,11 @@ const { customRow } = useCustomRow(getProps, {
const { getRowClassName } = useTableStyle(getProps, prefixCls)
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(getProps, tableData, emit)
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
getProps,
tableData,
emit,
)
const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => {
@ -160,18 +179,19 @@ const handlers: InnerHandlers = {
const { getHeaderProps } = useTableHeader(getProps, slots, handlers)
const { getFooterProps } = useTableFooter(getProps, getScrollRef, tableElRef, getDataSourceRef)
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange, getShowForm, setShowForm } = useTableForm(
const { getFooterProps } = useTableFooter(
getProps,
slots,
fetch,
getLoading,
getScrollRef,
tableElRef,
getDataSourceRef,
)
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange, getShowForm, setShowForm }
= useTableForm(getProps, slots, fetch, getLoading)
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef)
let propsData: Recordable = {
let propsData: any = {
...attrs,
customRow,
...unref(getProps),
@ -219,10 +239,6 @@ function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props }
}
function handleResizeColumn(w, col) {
col.width = w
}
const tableAction: TableActionType = {
reload,
getSelectRows,
@ -265,7 +281,7 @@ const tableAction: TableActionType = {
}
createTableContext({ ...tableAction, wrapRef, getBindValues })
defineExpose({ tableAction })
defineExpose(tableAction)
emit('register', tableAction, formActions)
</script>
@ -293,7 +309,7 @@ emit('register', tableAction, formActions)
v-bind="getBindValues"
:row-class-name="getRowClassName"
@change="handleTableChange"
@resize-column="handleResizeColumn"
@resize-column="setColumnWidth"
>
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
<slot :name="item" v-bind="data || {}" />
@ -315,9 +331,10 @@ emit('register', tableAction, formActions)
</template>
<style lang="less">
@border-color: #cecece4d;
@prefix-cls: ~'@{namespace}-basic-table';
html[data-theme='dark'] {
[data-theme='dark'] {
.ant-table-tbody > tr:hover.ant-table-row-selected > td,
.ant-table-tbody > tr.ant-table-row-selected td {
background-color: #262626;
@ -378,7 +395,7 @@ html[data-theme='dark'] {
//}
}
.ant-pagination {
.ant-table-wrapper .ant-pagination {
margin: 10px 0 0;
}

19
src/components/Table/src/components/TableAction.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, toRaw, unref } from 'vue'
import { DownOutlined } from '@ant-design/icons-vue'
import type { TooltipProps } from 'ant-design-vue'
@ -60,7 +61,7 @@ const getActions = computed(() => {
.map((action) => {
const { popConfirm } = action
return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
type: 'link',
...action,
...(popConfirm || {}),
@ -96,7 +97,7 @@ const getAlign = computed(() => {
function getTooltip(data: string | TooltipProps): TooltipProps {
return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
placement: 'bottom',
...(isString(data) ? { title: data } : data),
}
@ -130,9 +131,18 @@ function onCellClick(e: MouseEvent) {
{{ action.label }}
</template>
</PopConfirmButton>
<Divider v-if="divider && index < getActions.length - 1" type="vertical" class="action-divider" />
<Divider
v-if="divider && index < getActions.length - 1"
type="vertical"
class="action-divider"
/>
</template>
<Dropdown v-if="dropDownActions && getDropdownList.length > 0" :trigger="['hover']" :drop-menu-list="getDropdownList" popconfirm>
<Dropdown
v-if="dropDownActions && getDropdownList.length > 0"
:trigger="['hover']"
:drop-menu-list="getDropdownList"
popconfirm
>
<slot name="more" />
<a-button v-if="!$slots.more" type="link">
{{ t('action.more') }} <DownOutlined class="icon-more" />
@ -191,6 +201,7 @@ function onCellClick(e: MouseEvent) {
.icon-more {
margin-left: 0.25rem;
transform: rotate(90deg);
svg {
font-size: 1.1em;

2
src/components/Table/src/components/TableFooter.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, toRaw, unref } from 'vue'
import { Table } from 'ant-design-vue'
import { cloneDeep } from 'lodash-es'
@ -10,6 +11,7 @@ import { propTypes } from '@/utils/propTypes'
import { isFunction } from '@/utils/is'
defineOptions({ name: 'BasicTableFooter' })
const props = defineProps({
summaryFunc: {
type: Function as PropType<Fn>,

15
src/components/Table/src/components/TableHeader.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { Divider } from 'ant-design-vue'
import type { ColumnChangeParam, TableSetting } from '../types/table'
import TableSettingComponent from './settings/index.vue'
@ -9,7 +10,7 @@ defineOptions({ name: 'BasicTableHeader' })
defineProps({
title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
type: [Function, String] as PropType<string | ((data) => string)>,
},
tableSetting: {
type: Object as PropType<TableSetting>,
@ -37,11 +38,19 @@ function handleColumnChange(data: ColumnChangeParam[]) {
</div>
<div class="flex items-center">
<slot v-if="$slots.tableTitle" name="tableTitle" />
<TableTitle v-if="!$slots.tableTitle && title" :help-message="titleHelpMessage" :title="title" />
<TableTitle
v-if="!$slots.tableTitle && title"
:help-message="titleHelpMessage"
:title="title"
/>
<div :class="`${prefixCls}__toolbar`">
<slot name="toolbar" />
<Divider v-if="$slots.toolbar && showTableSetting" type="vertical" />
<TableSettingComponent v-if="showTableSetting" :setting="tableSetting" @columns-change="handleColumnChange" />
<TableSettingComponent
v-if="showTableSetting"
:setting="tableSetting"
@columns-change="handleColumnChange"
/>
</div>
</div>
</div>

27
src/components/Table/src/components/TableImg.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed } from 'vue'
import { Badge, Image, ImagePreviewGroup } from 'ant-design-vue'
import { Badge, Image } from 'ant-design-vue'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
@ -20,11 +20,12 @@ const props = defineProps({
srcPrefix: propTypes.string.def(''),
// fallback,
fallback: propTypes.string.def(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==',
),
})
const PreviewGroup = Image.PreviewGroup
const getWrapStyle = computed((): CSSProperties => {
const { size } = props
const s = `${size}px`
@ -35,10 +36,15 @@ const { prefixCls } = useDesign('basic-table-img')
</script>
<template>
<div v-if="imgList && imgList.length" :class="prefixCls" class="mx-auto flex items-center" :style="getWrapStyle">
<div
v-if="imgList && imgList.length"
:class="prefixCls"
class="mx-auto flex items-center"
:style="getWrapStyle"
>
<Badge v-if="simpleShow" :count="!showBadge || imgList.length === 1 ? 0 : imgList.length">
<div class="img-div">
<ImagePreviewGroup>
<PreviewGroup>
<template v-for="(img, index) in imgList" :key="img">
<Image
:width="size"
@ -49,14 +55,19 @@ const { prefixCls } = useDesign('basic-table-img')
:fallback="fallback"
/>
</template>
</ImagePreviewGroup>
</PreviewGroup>
</div>
</Badge>
<ImagePreviewGroup v-else>
<PreviewGroup v-else>
<template v-for="(img, index) in imgList" :key="img">
<Image :width="size" :style="{ marginLeft: index === 0 ? 0 : `${margin}px` }" :src="srcPrefix + img" :fallback="fallback" />
<Image
:width="size"
:style="{ marginLeft: index === 0 ? 0 : `${margin}px` }"
:src="srcPrefix + img"
:fallback="fallback"
/>
</template>
</ImagePreviewGroup>
</PreviewGroup>
</div>
</template>

5
src/components/Table/src/components/TableTitle.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed } from 'vue'
import { BasicTitle } from '@/components/Basic'
import { useDesign } from '@/hooks/web/useDesign'
@ -8,10 +9,10 @@ defineOptions({ name: 'BasicTableTitle' })
const props = defineProps({
title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
type: [Function, String] as PropType<string | ((data) => string)>,
},
getSelectRows: {
type: Function as PropType<() => Recordable[]>,
type: Function as PropType<() => any[]>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,

63
src/components/Table/src/components/editable/EditableCell.vue

@ -1,6 +1,6 @@
<!-- eslint-disable vue/no-mutating-props -->
<script lang="tsx">
import type { CSSProperties } from 'vue'
import type { CSSProperties, PropType } from 'vue'
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue'
import { pick, set } from 'lodash-es'
@ -8,9 +8,8 @@ import { Spin } from 'ant-design-vue'
import type { BasicColumn } from '../../types/table'
import { useTableContext } from '../../hooks/useTableContext'
import { CellComponent } from './CellComponent'
import { createPlaceholderMessage } from './helper'
import type { EditRecordRow } from './index'
import { createPlaceholderMessage } from './helper'
import { useDesign } from '@/hooks/web/useDesign'
import clickOutside from '@/directives/clickOutside'
@ -27,11 +26,13 @@ export default defineComponent({
},
props: {
value: {
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
type: [String, Number, Boolean, Object] as PropType<
string | number | boolean | Record<string, any>
>,
default: '',
},
record: {
type: Object as PropType<EditRecordRow>,
type: Object as any,
},
column: {
type: Object as PropType<BasicColumn>,
@ -45,7 +46,7 @@ export default defineComponent({
const elRef = ref()
const ruleOpen = ref(false)
const ruleMessage = ref('')
const optionsRef = ref<LabelValueOptions>([])
const optionsRef = ref([])
const currentValueRef = ref<any>(props.value)
const defaultValueRef = ref<any>(props.value)
const spinning = ref<boolean>(false)
@ -63,7 +64,6 @@ export default defineComponent({
const component = unref(getComponent)
return ['Checkbox', 'Switch'].includes(component)
})
const getDisable = computed(() => {
const { editDynamicDisabled } = props.column
let disabled = false
@ -85,7 +85,7 @@ export default defineComponent({
const value = isCheckValue ? (isNumber(val) || isBoolean(val) ? val : !!val) : val
let compProps = props.column?.editComponentProps ?? {}
let compProps = props.column?.editComponentProps ?? ({} as any)
const { record, column, index } = props
if (isFunction(compProps))
@ -96,7 +96,7 @@ export default defineComponent({
delete compProps.onChange
const component = unref(getComponent)
const apiSelectProps: Recordable = {}
const apiSelectProps: Record<string, any> = {}
if (component === 'ApiSelect')
apiSelectProps.cache = true
@ -133,12 +133,11 @@ export default defineComponent({
if (!component.includes('Select') && !component.includes('Radio'))
return value
const options: LabelValueOptions = unref(getComponentProps)?.options ?? (unref(optionsRef) || [])
const options = unref(getComponentProps)?.options ?? (unref(optionsRef) || [])
const option = options.find(item => `${item.value}` === `${value}`)
return option?.label ?? value
})
const getRowEditable = computed(() => {
const { editable } = props.record || {}
return !!editable
@ -184,12 +183,16 @@ export default defineComponent({
const component = unref(getComponent)
if (!e)
currentValueRef.value = e
else if (component === 'Checkbox')
currentValueRef.value = (e as ChangeEvent).target.checked
currentValueRef.value = e.target.checked
else if (component === 'Switch')
currentValueRef.value = e
else if (e?.target && Reflect.has(e.target, 'value'))
currentValueRef.value = (e as ChangeEvent).target.value
currentValueRef.value = e.target.value
else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e))
currentValueRef.value = e
@ -219,7 +222,7 @@ export default defineComponent({
return false
}
if (isFunction(editRule)) {
const res = await editRule(currentValue, record as Recordable)
const res = await editRule(currentValue, record)
if (res) {
ruleMessage.value = res
ruleOpen.value = true
@ -259,7 +262,9 @@ export default defineComponent({
if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
spinning.value = true
const keys: string[] = columns.map(_column => _column.dataIndex).filter(field => !!field) as string[]
const keys: string[] = columns
.map(_column => _column.dataIndex)
.filter(field => !!field) as string[]
let result: any = true
try {
result = await beforeEditSubmit({
@ -322,28 +327,31 @@ export default defineComponent({
}
// only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) {
function handleOptionsChange(options) {
const { replaceFields } = unref(getComponentProps)
const component = unref(getComponent)
if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}
let listOptions: Recordable[] = treeToList(options, { children })
let listOptions = treeToList(options, { children })
listOptions = listOptions.map((item) => {
return {
label: item[title],
value: item[value],
}
})
optionsRef.value = listOptions as LabelValueOptions
optionsRef.value = listOptions
}
else {
optionsRef.value = options
}
}
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
if (props.record)
isArray(props.record[cbs]) ? props.record[cbs]?.push(handle) : (props.record[cbs] = [handle])
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle) {
if (props.record) {
isArray(props.record[cbs])
? props.record[cbs]?.push(handle)
: (props.record[cbs] = [handle])
}
}
if (props.record) {
@ -434,7 +442,10 @@ export default defineComponent({
/>
{!this.getRowEditable && (
<div class={`${this.prefixCls}__action`}>
<CheckOutlined class={[`${this.prefixCls}__icon`, 'mx-2']} onClick={this.handleSubmitClick} />
<CheckOutlined
class={[`${this.prefixCls}__icon`, 'mx-2']}
onClick={this.handleSubmitClick}
/>
<CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
</div>
)}
@ -477,10 +488,12 @@ export default defineComponent({
.edit-cell-rule-popover {
.ant-popover-inner-content {
padding: 4px 8px;
color: @error-color;
// border: 1px solid @error-color;
border-radius: 2px;
}
}
.@{prefix-cls} {
position: relative;
min-height: 24px; // hover
@ -490,7 +503,7 @@ export default defineComponent({
align-items: center;
justify-content: center;
> .ant-select {
>.ant-select {
min-width: calc(100% - 50px);
}
}
@ -498,6 +511,10 @@ export default defineComponent({
&__icon {
&:hover {
transform: scale(1.2);
svg {
color: @primary-color;
}
}
}

42
src/components/Table/src/components/settings/ColumnSetting.vue

@ -16,10 +16,6 @@ import { useDesign } from '@/hooks/web/useDesign'
import { isFunction, isNullAndUnDef } from '@/utils/is'
import { getPopupContainer as getParentContainer } from '@/utils'
defineOptions({ name: 'ColumnSetting' })
const emit = defineEmits(['columns-change'])
interface State {
checkAll: boolean
isInit?: boolean
@ -33,6 +29,10 @@ interface Options {
fixed?: boolean | 'left' | 'right'
}
defineOptions({ name: 'ColumnSetting' })
const emit = defineEmits(['columns-change'])
const CheckboxGroup = Checkbox.Group
const attrs = useAttrs()
@ -51,7 +51,7 @@ const plainOptions = ref<Options[] | any>([])
const plainSortOptions = ref<Options[]>([])
const columnListRef = ref<ComponentRef>(null)
const columnListRef = ref(null)
const state = reactive<State>({
checkAll: true,
@ -124,7 +124,6 @@ async function init(isReset = false) {
return item.dataIndex || item.title
})
.filter(Boolean) as string[]
plainOptions.value = columns
plainSortOptions.value = columns
//
@ -136,13 +135,14 @@ async function init(isReset = false) {
state.checkAll = checkList.length === columns.length
inited = false
handleOpenChange()
state.checkedList = checkList
}
// checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainSortOptions.value.map(item => item.value)
plainSortOptions.value.forEach(item => ((item as BasicColumn).defaultHidden = !e.target.checked))
plainSortOptions.value.forEach(
item => ((item as BasicColumn).defaultHidden = !e.target.checked),
)
if (e.target.checked) {
state.checkedList = checkList
setColumns(checkList)
@ -169,7 +169,7 @@ function onChange(checkedList: string[]) {
return sortList.indexOf(prev) - sortList.indexOf(next)
})
unref(plainSortOptions).forEach((item) => {
;(item as BasicColumn).defaultHidden = !checkedList.includes(item.value)
(item as BasicColumn).defaultHidden = !checkedList.includes(item.value)
})
setColumns(checkedList)
}
@ -197,7 +197,7 @@ function handleOpenChange() {
const columnListEl = unref(columnListRef)
if (!columnListEl)
return
const el = columnListEl.$el as any
const el = (columnListEl as any).$el
if (!el)
return
// Drag and drop sort
@ -256,7 +256,9 @@ function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string))
return
const columns = getColumns().filter((c: BasicColumn) => state.checkedList.includes(c.dataIndex as string)) as BasicColumn[]
const columns = getColumns().filter((c: BasicColumn) =>
state.checkedList.includes(c.dataIndex as string),
) as BasicColumn[]
const isFixed = item.fixed === fixed ? false : fixed
const index = columns.findIndex(col => col.dataIndex === item.dataIndex)
if (index !== -1)
@ -278,7 +280,10 @@ function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns)
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const open
= columns.findIndex((c: BasicColumn | string) => c === col.value || (typeof c !== 'string' && c.dataIndex === col.value)) !== -1
= columns.findIndex(
(c: BasicColumn | string) =>
c === col.value || (typeof c !== 'string' && c.dataIndex === col.value),
) !== -1
return { dataIndex: col.value, fixed: col.fixed, open }
})
@ -286,7 +291,9 @@ function setColumns(columns: BasicColumn[] | string[]) {
}
function getPopupContainer() {
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer()
return isFunction(attrs.getPopupContainer)
? attrs.getPopupContainer()
: getParentContainer()
}
function updateSortOption(column: BasicColumn) {
@ -406,6 +413,10 @@ function updateSortOption(column: BasicColumn) {
.ant-checkbox-wrapper {
width: 100%;
&:hover {
color: @primary-color;
}
}
}
@ -414,6 +425,11 @@ function updateSortOption(column: BasicColumn) {
color: rgb(0 0 0 / 45%);
cursor: pointer;
&.active,
&:hover {
color: @primary-color;
}
&.disabled {
color: @disabled-color;
cursor: not-allowed;

7
src/components/Table/src/components/settings/index.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, unref } from 'vue'
import type { ColumnChangeParam, TableSetting } from '../../types/table'
import { useTableContext } from '../../hooks/useTableContext'
@ -44,7 +45,11 @@ function getTableContainer() {
<RedoSetting v-if="getSetting.redo" :get-popup-container="getTableContainer" />
<FormSetting v-if="getSetting.form" :get-popup-container="getTableContainer" />
<SizeSetting v-if="getSetting.size" :get-popup-container="getTableContainer" />
<ColumnSetting v-if="getSetting.setting" :get-popup-container="getTableContainer" @columns-change="handleColumnChange" />
<ColumnSetting
v-if="getSetting.setting"
:get-popup-container="getTableContainer"
@columns-change="handleColumnChange"
/>
<FullScreenSetting v-if="getSetting.fullScreen" :get-popup-container="getTableContainer" />
</div>
</template>

9
src/components/Table/src/const.ts

@ -2,7 +2,14 @@ import componentSetting from '@/settings/componentSetting'
const { table } = componentSetting
const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSize, defaultSortFn, defaultFilterFn } = table
const {
pageSizeOptions,
defaultPageSize,
fetchSetting,
defaultSize,
defaultSortFn,
defaultFilterFn,
} = table
export const ROW_KEY = 'key'

5
src/components/Table/src/hooks/useColumns.ts

@ -103,7 +103,10 @@ function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: Bas
}
}
export function useColumns(propsRef: ComputedRef<BasicTableProps>, getPaginationRef: ComputedRef<boolean | PaginationProps>) {
export function useColumns(
propsRef: ComputedRef<BasicTableProps>,
getPaginationRef: ComputedRef<boolean | PaginationProps>,
) {
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>
let cacheColumns = unref(propsRef).columns

15
src/components/Table/src/hooks/useCustomRow.ts

@ -13,7 +13,11 @@ interface Options {
getAutoCreateKey: ComputedRef<boolean | undefined>
}
function getKey(record: Recordable, rowKey: string | ((record: Record<string, any>) => string) | undefined, autoCreateKey?: boolean) {
function getKey(
record: Recordable,
rowKey: string | ((record: Record<string, any>) => string) | undefined,
autoCreateKey?: boolean,
) {
if (!rowKey || autoCreateKey)
return record[ROW_KEY]
@ -40,13 +44,15 @@ export function useCustomRow(
return
const keys = getSelectRowKeys() || []
const key = getKey(record, rowKey, unref(getAutoCreateKey))
if (!key)
if (key === null)
return
const isCheckbox = rowSelection.type === 'checkbox'
if (isCheckbox) {
// 找到tr
const tr: HTMLElement = (e as MouseEvent).composedPath?.().find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement
const tr = (e as MouseEvent)
.composedPath?.()
.find(dom => (dom as HTMLElement).tagName === 'TR') as HTMLElement
if (!tr)
return
// 找到Checkbox,检查是否为disabled
@ -54,7 +60,8 @@ export function useCustomRow(
if (!checkBox || checkBox.hasAttribute('disabled'))
return
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key])
keys.push(key)
setSelectedRowKeys(keys)
return
}
const keyIndex = keys.findIndex(item => item === key)

54
src/components/Table/src/hooks/useDataSource.ts

@ -2,11 +2,11 @@ import type { ComputedRef, Ref } from 'vue'
import { computed, onMounted, reactive, ref, unref, watch, watchEffect } from 'vue'
import { useTimeoutFn } from '@vueuse/core'
import { cloneDeep, get, merge } from 'lodash-es'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import type { PaginationProps } from '../types/pagination'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const'
import { buildUUID } from '@/utils/uuid'
import { isBoolean, isFunction, isObject } from '@/utils/is'
import { buildUUID } from '@/utils/uuid'
interface ActionType {
getPaginationInfo: ComputedRef<boolean | PaginationProps>
@ -23,7 +23,14 @@ interface SearchState {
}
export function useDataSource(
propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, getFieldsValue, clearSelectedRowKeys, tableData }: ActionType,
{
getPaginationInfo,
setPagination,
setLoading,
getFieldsValue,
clearSelectedRowKeys,
tableData,
}: ActionType,
emit: EmitType,
) {
const searchState = reactive<SearchState>({
@ -48,7 +55,11 @@ export function useDataSource(
},
)
function handleTableChange(pagination: PaginationProps, filters: Partial<Recordable<string[]>>, sorter: SorterResult) {
function handleTableChange(
pagination: PaginationProps,
filters: Partial<Recordable<string[]>>,
sorter: SorterResult,
) {
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef)
if (clearSelectOnPageChange)
clearSelectedRowKeys()
@ -117,7 +128,7 @@ export function useDataSource(
return unref(dataSourceRef)
})
function updateTableData(index: number, key: string, value: any) {
async function updateTableData(index: number, key: string, value: any) {
const record = dataSourceRef.value[index]
if (record)
dataSourceRef.value[index][key] = value
@ -125,7 +136,10 @@ export function useDataSource(
return dataSourceRef.value[index]
}
function updateTableDataRecord(rowKey: string | number, record: Recordable): Recordable | undefined {
function updateTableDataRecord(
rowKey: string | number,
record: Recordable,
): Recordable | undefined {
const row = findTableDataRecord(rowKey)
if (row) {
@ -185,7 +199,10 @@ export function useDataSource(
})
}
function insertTableDataRecord(record: Recordable | Recordable[], index?: number): Recordable[] | undefined {
function insertTableDataRecord(
record: Recordable | Recordable[],
index?: number,
): Recordable[] | undefined {
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length
const _record = isObject(record) ? [record as Recordable] : (record as Recordable[])
@ -207,7 +224,7 @@ export function useDataSource(
let ret
array.some(function iter(r) {
if (typeof rowKeyName === 'function') {
if ((rowKeyName(r)) === rowKey) {
if ((rowKeyName(r) as string) === rowKey) {
ret = r
return true
}
@ -234,12 +251,25 @@ export function useDataSource(
}
async function fetch(opt?: FetchParams) {
const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = unref(propsRef)
const {
api,
searchInfo,
defSort,
fetchSetting,
beforeFetch,
afterFetch,
useSearchForm,
pagination,
} = unref(propsRef)
if (!api || !isFunction(api))
return
try {
setLoading(true)
const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting)
const { pageField, sizeField, listField, totalField } = Object.assign(
{},
FETCH_SETTING,
fetchSetting,
)
let pageParams: Recordable = {}
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps
@ -317,8 +347,8 @@ export function useDataSource(
}
}
function setTableData<T extends Ref<Recordable<any>[]>>(values: T[]) {
dataSourceRef.value = values
function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values as Recordable[]
}
function getDataSource<T = Recordable>() {

3
src/components/Table/src/hooks/usePagination.tsx

@ -16,6 +16,7 @@ interface ItemRender {
function itemRender({ page, type, originalElement }: ItemRender) {
if (type === 'prev')
return page === 0 ? null : <LeftOutlined />
else if (type === 'next')
return page === 1 ? null : <RightOutlined />
@ -76,7 +77,7 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
return unref(show)
}
function setShowPagination(flag: boolean) {
async function setShowPagination(flag: boolean) {
show.value = flag
}

10
src/components/Table/src/hooks/useScrollTo.ts

@ -2,12 +2,17 @@ import type { ComputedRef, Ref } from 'vue'
import { nextTick, unref } from 'vue'
import { warn } from '@/utils/log'
export function useTableScrollTo(tableElRef: Ref<ComponentRef>, getDataSourceRef: ComputedRef<Recordable[]>) {
export function useTableScrollTo(
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable[]>,
) {
let bodyEl: HTMLElement | null
async function findTargetRowToScroll(targetRowData: Recordable) {
const { id } = targetRowData
const targetRowEl: HTMLElement | null | undefined = bodyEl?.querySelector(`[data-row-key="${id}"]`)
const targetRowEl: HTMLElement | null | undefined = bodyEl?.querySelector(
`[data-row-key="${id}"]`,
)
// Add a delay to get new dataSource
await nextTick()
bodyEl?.scrollTo({
@ -46,6 +51,7 @@ export function useTableScrollTo(tableElRef: Ref<ComponentRef>, getDataSourceRef
const targetRowData = dataSource.find(data => data.id === pos)
if (targetRowData)
findTargetRowToScroll(targetRowData)
else
warn(`id: ${pos} doesn't exist`)
}

15
src/components/Table/src/hooks/useTable.ts

@ -1,5 +1,6 @@
import type { WatchStopHandle } from 'vue'
import { onUnmounted, ref, toRaw, unref, watch } from 'vue'
import type { Key } from 'ant-design-vue/lib/table/interface'
import type { BasicColumn, BasicTableProps, FetchParams, TableActionType } from '../types/table'
import type { PaginationProps } from '../types/pagination'
import type { DynamicProps } from '@/types/utils'
@ -57,9 +58,11 @@ export function useTable(tableProps?: Props): [
function getTableInstance(): TableActionType {
const table = unref(tableRef)
if (!table)
error('The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!')
if (!table) {
error(
'The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!',
)
}
return table as TableActionType
}
@ -91,7 +94,7 @@ export function useTable(tableProps?: Props): [
const columns = getTableInstance().getColumns({ ignoreIndex }) || []
return toRaw(columns)
},
setColumns: (columns: BasicColumn[]) => {
setColumns: (columns: BasicColumn[] | string[]) => {
getTableInstance().setColumns(columns)
},
setTableData: (values: any[]) => {
@ -112,7 +115,7 @@ export function useTable(tableProps?: Props): [
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys()
},
setSelectedRowKeys: (keys: string[] | number[]) => {
setSelectedRowKeys: (keys: (string | number)[]) => {
getTableInstance().setSelectedRowKeys(keys)
},
getPaginationRef: () => {
@ -154,7 +157,7 @@ export function useTable(tableProps?: Props): [
expandAll: () => {
getTableInstance().expandAll()
},
expandRows: (keys: string[]) => {
expandRows: (keys: Key[]) => {
getTableInstance().expandRows(keys)
},
collapseAll: () => {

6
src/components/Table/src/hooks/useTableExpand.ts

@ -3,7 +3,11 @@ import { computed, ref, toRaw, unref } from 'vue'
import type { BasicTableProps } from '../types/table'
import { ROW_KEY } from '../const'
export function useTableExpand(propsRef: ComputedRef<BasicTableProps>, tableData: Ref<Recordable[]>, emit: EmitType) {
export function useTableExpand(
propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const expandedRowKeys = ref<(string | number)[]>([])
const getAutoCreateKey = computed(() => {

8
src/components/Table/src/hooks/useTableFooter.ts

@ -20,7 +20,9 @@ export function useTableFooter(
const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary, summaryData } = unref(propsRef)
return showSummary && !unref(getIsEmptyData) ? () => h(TableFooter, { summaryFunc, summaryData, scroll: unref(scrollRef) }) : undefined
return showSummary && !unref(getIsEmptyData)
? () => h(TableFooter, { summaryFunc, summaryData, scroll: unref(scrollRef) })
: undefined
})
watchEffect(() => {
@ -41,7 +43,9 @@ export function useTableFooter(
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-content') as HTMLDivElement
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-content',
) as HTMLDivElement
if (!footerBodyDom || !bodyDom)
return
footerBodyDom.scrollLeft = bodyDom.scrollLeft

6
src/components/Table/src/hooks/useTableForm.ts

@ -25,13 +25,15 @@ export function useTableForm(
const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
const keys = Object.keys(slots)
return keys.map(item => (item.startsWith('form-') ? item : null)).filter(item => !!item) as string[]
return keys
.map(item => (item.startsWith('form-') ? item : null))
.filter(item => !!item) as string[]
})
function replaceFormSlotKey(key: string) {
if (!key)
return ''
return key?.replace?.(/form\-/, '') ?? ''
return key?.replace?.(/form-/, '') ?? ''
}
function handleSearchInfoChange(info: Recordable) {

6
src/components/Table/src/hooks/useTableHeader.ts

@ -5,7 +5,11 @@ import TableHeader from '../components/TableHeader.vue'
import { isString } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper'
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots, handlers: InnerHandlers) {
export function useTableHeader(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
handlers: InnerHandlers,
) {
const getHeaderProps = computed((): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef)
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting

4
src/components/Table/src/hooks/useTableScroll.ts

@ -179,7 +179,7 @@ export function useTableScroll(
handleScrollBar(bodyEl, tableEl)
bodyEl.style.height = 'unset'
bodyEl!.style.height = 'unset'
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0)
return
@ -207,7 +207,7 @@ export function useTableScroll(
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height
setHeight(height)
bodyEl.style.height = `${height}px`
bodyEl!.style.height = `${height}px`
}
useWindowSizeFn(calcTableHeight, { wait: 280 })
onMountedOrActivated(() => {

20
src/components/Table/src/props.ts

@ -1,5 +1,14 @@
import type { PropType } from 'vue'
import type { PaginationProps } from './types/pagination'
import type { BasicColumn, FetchSetting, SizeType, SorterResult, TableCustomRecord, TableRowSelection, TableSetting } from './types/table'
import type {
BasicColumn,
FetchSetting,
SizeType,
SorterResult,
TableCustomRecord,
TableRowSelection,
TableSetting,
} from './types/table'
import { DEFAULT_FILTER_FN, DEFAULT_SIZE, DEFAULT_SORT_FN, FETCH_SETTING } from './const'
import type { FormProps } from '@/components/Form'
@ -126,7 +135,14 @@ export const basicProps = {
default: null,
},
beforeEditSubmit: {
type: Function as PropType<(data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>>,
type: Function as PropType<
(data: {
record: Recordable
index: number
key: string | number
value: any
}) => Promise<any>
>,
},
size: {
type: String as PropType<SizeType>,

13
src/components/Table/src/types/column.ts

@ -71,10 +71,13 @@ export interface ColumnProps<T> {
* Customized filter overlay
* @type any (slot)
*/
filterDropdown?: VNodeChild | JSX.Element | ((props: FilterDropdownProps) => VNodeChild | JSX.Element)
filterDropdown?:
| VNodeChild
| JSX.Element
| ((props: FilterDropdownProps) => VNodeChild | JSX.Element)
/**
* Whether filterDropdown is open
* Whether filterDropdown is visible
* @type boolean
*/
filterDropdownOpen?: boolean
@ -135,7 +138,7 @@ export interface ColumnProps<T> {
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
* @type boolean | Function
*/
sorter?: boolean | Fn
sorter?: boolean | Function
/**
* Order of sorted values: 'ascend' 'descend' false
@ -181,10 +184,10 @@ export interface ColumnProps<T> {
onFilter?: (value: any, record: T) => boolean
/**
* Callback executed when filterDropdownOpen is changed, Use as a filterDropdownOpen event when using template or jsx
* Callback executed when filterDropdownVisible is changed, Use as a filterDropdownVisible event when using template or jsx
* @type Function
*/
onFilterDropdownOpenChange?: (open: boolean) => void
onFilterDropdownVisibleChange?: (visible: boolean) => void
/**
* When using columns, you can setting this property to configure the properties that support the slot,

8
src/components/Table/src/types/pagination.ts

@ -7,7 +7,13 @@ interface PaginationRenderProps {
originalElement: any
}
type PaginationPositon = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight'
type PaginationPositon =
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight'
export declare class PaginationConfig extends Pagination {
position?: PaginationPositon[]

43
src/components/Table/src/types/table.ts

@ -1,4 +1,4 @@
import type { Ref, VNodeChild } from 'vue'
import type { VNodeChild } from 'vue'
import type { TableRowSelection as ITableRowSelection, Key } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table'
import type { PaginationProps } from './pagination'
@ -55,6 +55,11 @@ export interface ColumnFilterItem {
children?: any
}
export interface TableCustomRecord<T = Recordable> {
record?: T
index?: number
}
export interface SorterResult {
column: ColumnProps
order: SortOrder
@ -89,7 +94,7 @@ export interface TableActionType {
getSelectRowKeys: () => Key[]
deleteSelectRowByKey: (key: string) => void
setPagination: (info: Partial<PaginationProps>) => void
setTableData: <T extends Ref<Recordable<any>[]>>(values: T[]) => void
setTableData: <T = Recordable>(values: T[]) => void
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => Recordable[] | void
@ -108,7 +113,7 @@ export interface TableActionType {
getCacheColumns: () => BasicColumn[]
emit?: EmitType
updateTableData: (index: number, key: string, value: any) => Recordable
setShowPagination: (show: boolean) => void
setShowPagination: (show: boolean) => Promise<void>
getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void
setCacheColumns?: (columns: BasicColumn[]) => void
@ -257,7 +262,7 @@ export interface BasicTableProps<T = any> {
* Customize row expand Icon.
* @type Function | VNodeChild
*/
expandIcon?: Fn | VNodeChild | JSX.Element
expandIcon?: Function | VNodeChild | JSX.Element
/**
* Whether to expand row by clicking anywhere in the whole row
@ -275,7 +280,7 @@ export interface BasicTableProps<T = any> {
* Table footer renderer
* @type Function | VNodeChild
*/
footer?: Fn | VNodeChild | JSX.Element
footer?: Function | VNodeChild | JSX.Element
/**
* Indent size in pixels of tree data
@ -366,14 +371,19 @@ export interface BasicTableProps<T = any> {
*
* @version 1.5.4
*/
transformCellText?: Fn
transformCellText?: Function
/**
* Callback executed before editable cell submit value, not for row-editor
*
* The cell will not submit data while callback return false
*/
beforeEditSubmit?: (data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>
beforeEditSubmit?: (data: {
record: Recordable
index: number
key: string | number
value: any
}) => Promise<any>
/**
* Callback executed when pagination, filters or sorter is changed
@ -401,14 +411,19 @@ export interface BasicTableProps<T = any> {
onColumnsChange?: (data: ColumnChangeParam[]) => void
}
export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map<string | number, any>
export type CellFormat =
| string
| ((text: string, record: Recordable, index: number) => string | number)
| Map<string | number, any>
export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[]
filters?: {
text: string
value: string
children?: unknown[] | (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]))
children?:
| unknown[]
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]))
}[]
//
@ -419,7 +434,6 @@ export interface BasicColumn extends ColumnProps<Recordable> {
// 自定义header渲染
customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element
// Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean
@ -434,7 +448,12 @@ export interface BasicColumn extends ColumnProps<Recordable> {
editable?: boolean
editComponent?: ComponentType
editComponentProps?:
| ((opt: { text: string | number | boolean | Recordable; record: Recordable; column: BasicColumn; index: number }) => Recordable)
| ((opt: {
text: string | number | boolean | Recordable
record: Recordable
column: BasicColumn
index: number
}) => Recordable)
| Recordable
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>)
editValueMap?: (value: any) => string
@ -457,7 +476,7 @@ export interface BasicColumn extends ColumnProps<Recordable> {
export interface ColumnChangeParam {
dataIndex: string
fixed: boolean | 'left' | 'right' | undefined
open: boolean
visible: boolean
}
export interface InnerHandlers {

13
src/hooks/core/useAttrs.ts

@ -1,7 +1,6 @@
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'
import type { Ref } from 'vue'
interface Params {
interface UseAttrsOptions {
excludeListeners?: boolean
excludeKeys?: string[]
excludeDefaultKeys?: boolean
@ -10,16 +9,16 @@ interface Params {
const DEFAULT_EXCLUDE_KEYS = ['class', 'style']
const LISTENER_PREFIX = /^on[A-Z]/
export function entries<T>(obj: Recordable<T>): [string, T][] {
function entries<T>(obj: Recordable<T>): [string, T][] {
return Object.keys(obj).map((key: string) => [key, obj[key]])
}
export function useAttrs(params: Params = {}): Ref<Recordable> | object {
function useAttrs(options: UseAttrsOptions = {}): Recordable<any> {
const instance = getCurrentInstance()
if (!instance)
return {}
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options
const attrs = shallowRef({})
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : [])
@ -32,10 +31,12 @@ export function useAttrs(params: Params = {}): Ref<Recordable> | object {
acm[key] = val
return acm
}, {} as Recordable)
}, {} as Recordable<any>)
attrs.value = res
})
return attrs
}
export { useAttrs, type UseAttrsOptions }

22
src/hooks/core/useRefs.ts

@ -1,16 +1,24 @@
import type { Ref } from 'vue'
import { onBeforeUpdate, ref } from 'vue'
import type { ComponentPublicInstance, Ref } from 'vue'
import { onBeforeUpdate, shallowRef } from 'vue'
export function useRefs(): [Ref<HTMLElement[]>, (index: number) => (el: HTMLElement) => void] {
const refs = ref([]) as Ref<HTMLElement[]>
function useRefs<T = HTMLElement>(): {
refs: Ref<T[]>
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void
} {
const refs = shallowRef([]) as Ref<T[]>
onBeforeUpdate(() => {
refs.value = []
})
const setRefs = (index: number) => (el: HTMLElement) => {
refs.value[index] = el
const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => {
refs.value[index] = el as T
}
return [refs, setRefs]
return {
refs,
setRefs,
}
}
export { useRefs }

16
src/hooks/event/useScrollTo.ts

@ -1,7 +1,6 @@
import { ref, unref } from 'vue'
import { isFunction, isUnDef } from '@/utils/is'
import { shallowRef, unref } from 'vue'
export interface ScrollToParams {
interface UseScrollToOptions {
el: any
to: number
duration?: number
@ -16,6 +15,7 @@ function easeInOutQuad(t: number, b: number, c: number, d: number) {
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
function move(el: HTMLElement, amount: number) {
el.scrollTop = amount
}
@ -23,13 +23,13 @@ function move(el: HTMLElement, amount: number) {
function position(el: HTMLElement) {
return el.scrollTop
}
export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams) {
const isActiveRef = ref(false)
function useScrollTo({ el, to, duration = 500, callback }: UseScrollToOptions) {
const isActiveRef = shallowRef(false)
const start = position(el)
const change = to - start
const increment = 20
let currentTime = 0
duration = isUnDef(duration) ? 500 : duration
const animateScroll = function () {
if (!unref(isActiveRef))
@ -42,7 +42,7 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
requestAnimationFrame(animateScroll)
}
else {
if (callback && isFunction(callback))
if (callback && typeof callback === 'function')
callback()
}
}
@ -57,3 +57,5 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
return { start: run, stop }
}
export { useScrollTo, type UseScrollToOptions }

3
src/hooks/event/useWindowSizeFn.ts

@ -1,4 +1,5 @@
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'
import type { AnyFunction } from '@/utils/types'
interface UseWindowSizeOptions {
wait?: number
@ -7,7 +8,7 @@ interface UseWindowSizeOptions {
listenerOptions?: AddEventListenerOptions | boolean
}
function useWindowSizeFn<T>(fn: Fn<T>, options: UseWindowSizeOptions = {}) {
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
const { wait = 150, immediate } = options
let handler = () => {
fn()

20
src/utils/types.ts

@ -14,7 +14,20 @@ type BiArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg: T[K])
export type EventEmitter<T extends Record<string, unknown>> = MonoArgEmitter<T, OptionalKeys<T>> & BiArgEmitter<T, RequiredKeys<T>>
export type AnyFunction<T> = (...args: any[]) => T
/**
*
*/
export type AnyPromiseFunction = (...arg: any[]) => PromiseLike<any>
/**
*
*/
export type AnyNormalFunction = (...arg: any[]) => any
/**
*
*/
export type AnyFunction = AnyNormalFunction | AnyPromiseFunction
export type PartialReturnType<T extends (...args: unknown[]) => unknown> = Partial<ReturnType<T>>
@ -22,6 +35,11 @@ export type SFCWithInstall<T> = T & Plugin
export type Nullable<T> = T | null
/**
*
*/
export type Recordable<T = any> = Record<string, T>
export type RefElement = Nullable<HTMLElement>
export type CustomizedHTMLElement<T> = HTMLElement & T

5
src/views/infra/codegen/components/data.ts

@ -123,11 +123,6 @@ export const basicInfoSchemas: FormSchema[] = [
component: 'ApiTreeSelect',
componentProps: {
api: () => listSimpleMenus(),
fieldNames: {
label: 'name',
key: 'id',
value: 'id',
},
handleTree: 'id',
},
colProps: { span: 12 },