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

49
src/components/Form/src/components/ApiCascader.vue

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

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

42
src/components/Form/src/components/ApiSelect.vue

@ -1,49 +1,46 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref, unref, watch } from 'vue' import { computed, ref, unref, watch } from 'vue'
import { Select } from 'ant-design-vue' import { Select } from 'ant-design-vue'
import type { SelectValue } from 'ant-design-vue/es/select'
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es'
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
import type { SelectValue } from 'ant-design-vue/lib/select'
import { isFunction } from '@/utils/is' import { isFunction } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem' import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
interface OptionsItem { label: string; value: string; disabled?: boolean }
defineOptions({ name: 'ApiSelect', inheritAttrs: false }) defineOptions({ name: 'ApiSelect', inheritAttrs: false })
const props = defineProps({ const props = defineProps({
value: { type: [Array, Object, String, Number] as PropType<SelectValue> }, value: { type: [Array, Object, String, Number] as PropType<SelectValue> },
numberToString: propTypes.bool, numberToString: propTypes.bool,
api: { api: {
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>, type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>,
default: null, default: null,
}, },
// api params // api params
params: { params: propTypes.any.def({}),
type: Object as PropType<Recordable>,
default: () => ({}),
},
// support xxx.xxx.xx // support xxx.xxx.xx
resultField: propTypes.string.def(''), resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'), labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'), valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true), immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(true), alwaysLoad: propTypes.bool.def(false),
options: { options: {
type: Array<OptionsItem>, type: Array<OptionsItem>,
default: [], default: [],
}, },
}) })
const emit = defineEmits(['options-change', 'change', 'update:value'])
const emit = defineEmits(['optionsChange', 'change', 'update:value'])
interface OptionsItem { label: string; value: string; disabled?: boolean }
const options = ref<OptionsItem[]>([]) const options = ref<OptionsItem[]>([])
const loading = ref(false) const loading = ref(false)
// //
const isFirstLoaded = ref(false) const isFirstLoaded = ref(false)
const emitData = ref<OptionsItem[]>([]) const emitData = ref<OptionsItem[]>([])
const { t } = useI18n() const { t } = useI18n()
// Embedded in the form, just use the hook binding to perform form verification // 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 getOptions = computed(() => {
const { labelField, valueField, numberToString } = props const { labelField, valueField, numberToString } = props
return unref(options).reduce((prev, next: Recordable) => { const data = unref(options).reduce((prev, next: any) => {
if (next) { if (next) {
const value = get(next, valueField) const value = get(next, valueField)
prev.push({ prev.push({
@ -63,6 +60,7 @@ const getOptions = computed(() => {
} }
return prev return prev
}, [] as OptionsItem[]) }, [] as OptionsItem[])
return data.length > 0 ? data : props.options
}) })
watch( watch(
@ -75,10 +73,6 @@ watch(
watch( watch(
() => props.params, () => props.params,
() => { () => {
if (props.alwaysLoad)
fetch()
else
!unref(isFirstLoaded) && fetch() !unref(isFirstLoaded) && fetch()
}, },
{ deep: true, immediate: props.immediate }, { deep: true, immediate: props.immediate },
@ -113,18 +107,15 @@ async function fetch() {
async function handleFetch(open: boolean) { async function handleFetch(open: boolean) {
if (open) { if (open) {
if (props.alwaysLoad) { if (props.alwaysLoad)
await fetch() await fetch()
} else if (!props.immediate && !unref(isFirstLoaded))
else if (!props.immediate && !unref(isFirstLoaded)) {
await fetch() await fetch()
isFirstLoaded.value = false
}
} }
} }
function emitChange() { function emitChange() {
emit('optionsChange', unref(getOptions)) emit('options-change', unref(getOptions))
} }
function handleChange(_, ...args) { function handleChange(_, ...args) {
@ -134,7 +125,10 @@ function handleChange(_, ...args) {
<template> <template>
<Select <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" @change="handleChange"
> >
<template v-for="item in Object.keys($slots)" #[item]="data"> <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> <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 { Transfer } from 'ant-design-vue'
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es'
import type { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer' import type { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
@ -9,16 +10,16 @@ import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'ApiTransfer' }) defineOptions({ name: 'ApiTransfer' })
const props = defineProps({ const props = defineProps({
value: { type: Array as PropType<Array<any>> }, value: { type: Array as PropType<Array<string>> },
api: { api: {
type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>, type: Function as PropType<(arg) => Promise<TransferItem[]>>,
default: null, default: null,
}, },
params: { type: Object }, params: { type: Object },
dataSource: { type: Array as PropType<Array<TransferItem>> }, dataSource: { type: Array as PropType<Array<TransferItem>> },
immediate: propTypes.bool.def(true), immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(true), alwaysLoad: propTypes.bool.def(false),
afterFetch: { type: Function as PropType<Fn> }, afterFetch: { type: Function },
resultField: propTypes.string.def(''), resultField: propTypes.string.def(''),
labelField: propTypes.string.def('title'), labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('key'), valueField: propTypes.string.def('key'),
@ -31,20 +32,22 @@ const props = defineProps({
showSelectAll: { type: Boolean, default: true }, showSelectAll: { type: Boolean, default: true },
targetKeys: { type: Array as PropType<Array<string>> }, targetKeys: { type: Array as PropType<Array<string>> },
}) })
const emit = defineEmits(['optionsChange', 'change']) const emit = defineEmits(['options-change', 'change'])
const attrs = useAttrs() // const attrs = useAttrs()
const _dataSource = ref<TransferItem[]>([]) const _dataSource = ref<TransferItem[]>([])
const _targetKeys = ref<string[]>([]) const _targetKeys = ref<string[]>([])
const getAttrs = computed(() => {
return { // const getAttrs = computed(() => {
...(!props.api ? { dataSource: unref(_dataSource) } : {}), // return {
...attrs, // ...(!props.api ? { dataSource: unref(_dataSource) } : {}),
} // ...attrs,
}) // }
// })
const getdataSource = computed(() => { const getdataSource = computed(() => {
const { labelField, valueField } = props const { labelField, valueField } = props
return unref(_dataSource).reduce((prev, next: Recordable) => {
return unref(_dataSource).reduce((prev, next) => {
if (next) { if (next) {
prev.push({ prev.push({
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
@ -56,9 +59,9 @@ const getdataSource = computed(() => {
}, [] as TransferItem[]) }, [] as TransferItem[])
}) })
const getTargetKeys = computed<string[]>(() => { const getTargetKeys = computed<string[]>(() => {
// if (unref(_targetKeys).length > 0) /* if (unref(_targetKeys).length > 0) {
// return unref(_targetKeys) return unref(_targetKeys);
} */
if (Array.isArray(props.value)) if (Array.isArray(props.value))
return props.value return props.value
@ -67,15 +70,18 @@ const getTargetKeys = computed<string[]>(() => {
return [] return []
}) })
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) { function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
_targetKeys.value = keys _targetKeys.value = keys
console.log(direction) console.log(direction)
console.log(moveKeys) console.log(moveKeys)
emit('change', keys) emit('change', keys)
} }
watchEffect(() => { watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch() props.immediate && !props.alwaysLoad && fetch()
}) })
watch( watch(
() => props.params, () => props.params,
() => { () => {
@ -83,6 +89,7 @@ watch(
}, },
{ deep: true }, { deep: true },
) )
async function fetch() { async function fetch() {
const api = props.api const api = props.api
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
@ -109,13 +116,12 @@ async function fetch() {
} }
} }
function emitChange() { function emitChange() {
emit('optionsChange', unref(getdataSource)) emit('options-change', unref(getdataSource))
} }
</script> </script>
<template> <template>
<Transfer <Transfer
v-bind="getAttrs"
:data-source="getdataSource" :data-source="getdataSource"
:filter-option="filterOption" :filter-option="filterOption"
:render="(item) => item.title" :render="(item) => item.title"

26
src/components/Form/src/components/ApiTree.vue

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

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

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

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

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

@ -1,7 +1,7 @@
<script lang="tsx"> <script lang="tsx">
import type { Ref } from 'vue' import type { PropType, Ref } from 'vue'
import { computed, defineComponent, toRefs, unref } 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 { Col, Divider, Form } from 'ant-design-vue'
import { cloneDeep, upperFirst } from 'lodash-es' import { cloneDeep, upperFirst } from 'lodash-es'
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from '../types/form' 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 { BasicHelp } from '@/components/Basic'
import { isBoolean, isFunction, isNull } from '@/utils/is' import { isBoolean, isFunction, isNull } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper' import { getSlot } from '@/utils/helper/tsxHelper'
import type { Nullable, Recordable } from '@/utils/types'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
export default defineComponent({ export default defineComponent({
@ -28,11 +29,11 @@ export default defineComponent({
default: () => ({}), default: () => ({}),
}, },
allDefaultValues: { allDefaultValues: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable<any>>,
default: () => ({}), default: () => ({}),
}, },
formModel: { formModel: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable<any>>,
default: () => ({}), default: () => ({}),
}, },
setFormModel: { setFormModel: {
@ -69,7 +70,7 @@ export default defineComponent({
...mergeDynamicData, ...mergeDynamicData,
...allDefaultValues, ...allDefaultValues,
...formModel, ...formModel,
} as Recordable, } as Recordable<any>,
schema, schema,
} }
}) })
@ -90,7 +91,7 @@ export default defineComponent({
componentProps, componentProps,
) )
} }
return componentProps as Recordable return componentProps as Recordable<any>
}) })
const getDisable = computed(() => { const getDisable = computed(() => {
@ -110,7 +111,11 @@ export default defineComponent({
function getShow(): { isShow: boolean; isIfShow: boolean } { function getShow(): { isShow: boolean; isIfShow: boolean } {
const { show, ifShow } = props.schema const { show, ifShow } = props.schema
const { showAdvancedButton } = props.formProps 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 isShow = true
let isIfShow = true let isIfShow = true
@ -131,18 +136,29 @@ export default defineComponent({
return { isShow, isIfShow } return { isShow, isIfShow }
} }
function handleRules(): Rule[] { function handleRules(): ValidationRule[] {
const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required } = props.schema const {
rules: defRules = [],
component,
rulesMessageJoinLabel,
label,
dynamicRules,
required,
} = props.schema
if (isFunction(dynamicRules)) 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 { 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 assertLabel = joinLabel ? label : ''
const defaultMsg = component ? createPlaceholderMessage(component) + assertLabel : assertLabel const defaultMsg = component
? createPlaceholderMessage(component) + assertLabel
: assertLabel
function validator(rule: any, value: any) { function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg 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) { if (requiredRuleIndex !== -1) {
const rule = rules[requiredRuleIndex] const rule = rules[requiredRuleIndex]
@ -213,9 +231,11 @@ export default defineComponent({
// Maximum input length rule check // Maximum input length rule check
const characterInx = rules.findIndex(val => val.max) const characterInx = rules.findIndex(val => val.max)
if (characterInx !== -1 && !rules[characterInx].validator) if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message = rules[characterInx].message || t('component.form.maxTip', [rules[characterInx].max] as Recordable) rules[characterInx].message
= rules[characterInx].message
|| t('component.form.maxTip', [rules[characterInx].max] as Recordable<any>)
}
return rules return rules
} }
@ -223,24 +243,29 @@ export default defineComponent({
if (!isComponentFormSchema(props.schema)) if (!isComponentFormSchema(props.schema))
return null 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 isCheck = component && ['Switch', 'Checkbox'].includes(component)
const eventKey = `on${upperFirst(changeEvent)}` const eventKey = `on${upperFirst(changeEvent)}`
const { autoSetPlaceHolder, size } = props.formProps const { autoSetPlaceHolder, size } = props.formProps
const propsData: Recordable<any> = {
const propsData: Recordable = {
allowClear: true, allowClear: true,
getPopupContainer: (trigger: Element) => trigger.parentNode!.parentNode, getPopupContainer: (trigger: Element) => trigger.parentNode,
size, size,
...unref(getComponentsProps), ...unref(getComponentsProps),
disabled: unref(getDisable), disabled: unref(getDisable),
} }
const on = { const on = {
[eventKey]: (...args: Nullable<Recordable>[]) => { [eventKey]: (...args: Nullable<Recordable<any>>[]) => {
const [e] = args const [e] = args
if (propsData[eventKey]) if (propsData[eventKey])
propsData[eventKey](...args) propsData[eventKey](...args)
@ -254,17 +279,18 @@ export default defineComponent({
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder
// RangePicker place is an array // RangePicker place is an array
if (isCreatePlaceholder && component !== 'RangePicker' && component) if (isCreatePlaceholder && component !== 'RangePicker' && component) {
propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component) propsData.placeholder
= unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
}
propsData.codeField = field propsData.codeField = field
propsData.formValues = unref(getValues) propsData.formValues = unref(getValues)
const bindValue: Recordable = { const bindValue: Recordable<any> = {
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field], [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
} }
const compAttr: Recordable = { const compAttr: Recordable<any> = {
...propsData, ...propsData,
...on, ...on,
...bindValue, ...bindValue,
@ -294,7 +320,9 @@ export default defineComponent({
: ( : (
label 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)) if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0))
return renderLabel return renderLabel
@ -310,9 +338,7 @@ export default defineComponent({
const { itemProps, slot, render, field, suffix, component } = props.schema const { itemProps, slot, render, field, suffix, component } = props.schema
const { labelCol, wrapperCol } = unref(itemLabelWidthProp) const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
const { colon } = props.formProps const { colon } = props.formProps
const opts = { disabled: unref(getDisable) } const opts = { disabled: unref(getDisable) }
if (component === 'Divider') { if (component === 'Divider') {
return ( return (
<Col span={24}> <Col span={24}>
@ -322,7 +348,11 @@ export default defineComponent({
} }
else { else {
const getContent = () => { 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 const showSuffix = !!suffix
@ -342,7 +372,7 @@ export default defineComponent({
name={field} name={field}
colon={colon} colon={colon}
class={{ 'suffix-item': showSuffix }} class={{ 'suffix-item': showSuffix }}
{...(itemProps as Recordable)} {...(itemProps as Recordable<any>)}
label={renderLabelHelpMessage()} label={renderLabelHelpMessage()}
rules={handleRules()} rules={handleRules()}
labelCol={labelCol} labelCol={labelCol}
@ -369,7 +399,11 @@ export default defineComponent({
const opts = { disabled: unref(getDisable) } const opts = { disabled: unref(getDisable) }
const getContent = () => { 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 ( 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 * @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> <script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { Radio } from 'ant-design-vue' import { Radio } from 'ant-design-vue'
import { isString } from '@/utils/is' import { isString } from '@/utils/is'
import { useRuleFormItem } from '@/hooks/component/useFormItem' import { useRuleFormItem } from '@/hooks/component/useFormItem'
import { useAttrs } from '@/hooks/core/useAttrs' import { useAttrs } from '@/hooks/core/useAttrs'
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean }
type RadioItem = string | OptionsItem
defineOptions({ name: 'RadioButtonGroup' }) defineOptions({ name: 'RadioButtonGroup' })
const props = defineProps({ const props = defineProps({
value: { value: {
@ -18,15 +22,12 @@ const props = defineProps({
default: () => [], default: () => [],
}, },
}) })
const emits = defineEmits(['change'])
const RadioButton = Radio.Button
const RadioGroup = Radio.Group
interface OptionsItem { label: string; value: string | number | boolean; disabled?: boolean } // const emits = defineEmits(['change'])
type RadioItem = string | OptionsItem
const RadioButton = Radio.Button
const RadioGroup = Radio.Group
const attrs = useAttrs() const attrs = useAttrs()
const emitData = ref<any[]>([]) const emitData = ref<any[]>([])
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData) 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[] return options.map(item => ({ label: item, value: item })) as OptionsItem[]
}) })
function handleClick(args) { function handleClick(...args) {
emitData.value = args emitData.value = args
emits('change', emitData.value)
} }
</script> </script>
<template> <template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid"> <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`"> <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 }} {{ item.label }}
</RadioButton> </RadioButton>
</template> </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 type { ComponentType } from './types'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { dateUtil } from '@/utils/dateUtil' import { dateUtil } from '@/utils/dateUtil'
@ -35,14 +35,20 @@ function genType() {
return [...DATE_TYPE, 'RangePicker'] 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')) if (Reflect.has(rule, 'type'))
return return
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component))
rule.type = valueFormat ? 'string' : 'object' rule.type = valueFormat ? 'string' : 'object'
else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component))
rule.type = 'array' rule.type = 'array'
else if (['InputNumber'].includes(component)) else if (['InputNumber'].includes(component))
rule.type = 'number' rule.type = 'number'
} }
@ -51,6 +57,7 @@ export function processDateValue(attr: Recordable, component: string) {
const { valueFormat, value } = attr const { valueFormat, value } = attr
if (valueFormat) if (valueFormat)
attr.value = isObject(value) ? dateUtil(value as unknown as Date).format(valueFormat) : value attr.value = isObject(value) ? dateUtil(value as unknown as Date).format(valueFormat) : value
else if (DATE_TYPE.includes(component) && value) else if (DATE_TYPE.includes(component) && value)
attr.value = dateUtil(attr.value) attr.value = dateUtil(attr.value)
} }
@ -74,7 +81,6 @@ export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch',
// TODO 自定义组件封装会出现验证问题,因此这里目前改成手动触发验证 // TODO 自定义组件封装会出现验证问题,因此这里目前改成手动触发验证
export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [ export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'Upload', 'Upload',
'ApiSelect',
'ApiTransfer', 'ApiTransfer',
'ApiTree', 'ApiTree',
'ApiTreeSelect', 'ApiTreeSelect',
@ -82,4 +88,6 @@ export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'ApiCascader', 'ApiCascader',
'AutoComplete', 'AutoComplete',
'RadioButtonGroup', '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 const xxlWidth = Number.parseInt(itemCol.xxl as string) || xlWidth
if (width <= screenEnum.LG) if (width <= screenEnum.LG)
itemColSum += mdWidth itemColSum += mdWidth
else if (width < screenEnum.XL) else if (width < screenEnum.XL)
itemColSum += lgWidth itemColSum += lgWidth
else if (width < screenEnum.XXL) else if (width < screenEnum.XXL)
itemColSum += xlWidth itemColSum += xlWidth
else else
itemColSum += xxlWidth itemColSum += xxlWidth
@ -84,7 +87,10 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
advanceState.hideAdvanceBtn = true advanceState.hideAdvanceBtn = true
advanceState.isAdvanced = 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 advanceState.hideAdvanceBtn = false
// More than 3 lines collapsed by default // More than 3 lines collapsed by default
@ -131,7 +137,10 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
} }
if (isShow && (colProps || baseColProps)) { if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced({ ...baseColProps, ...colProps }, itemColSum) const { itemColSum: sum, isAdvanced } = getAdvanced(
{ ...baseColProps, ...colProps },
itemColSum,
)
itemColSum = sum || 0 itemColSum = sum || 0
if (isAdvanced) if (isAdvanced)

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

@ -8,7 +8,12 @@ interface UseAutoFocusContext {
isInitedDefault: Ref<boolean> isInitedDefault: Ref<boolean>
formElRef: Ref<FormActionType> formElRef: Ref<FormActionType>
} }
export function useAutoFocus({ getSchema, getProps, formElRef, isInitedDefault }: UseAutoFocusContext) { export async function useAutoFocus({
getSchema,
getProps,
formElRef,
isInitedDefault,
}: UseAutoFocusContext) {
watchEffect(async () => { watchEffect(async () => {
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem)
return 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 return unref(formRef)?.getFieldsValue() as T
}, },
setFieldsValue: async (values: Recordable) => { setFieldsValue: async <T extends Recordable<any>>(values: T) => {
const form = await getForm() const form = await getForm()
form.setFieldsValue(values) 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() const form = await getForm()
form.appendSchemaByField(schema, prefixField, first) form.appendSchemaByField(schema, prefixField, first)
}, },
@ -101,7 +105,7 @@ export function useForm(props?: Props): UseFormReturnType {
return form.submit() 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() const form = await getForm()
return form.validate(nameList) return form.validate(nameList)
}, },

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

@ -91,7 +91,7 @@ export function useFormEvents({
if (fieldKeys.length) { if (fieldKeys.length) {
// eslint-disable-next-line array-callback-return // eslint-disable-next-line array-callback-return
fieldKeys.map((field) => { fieldKeys.map((field) => {
formModel[field] = defaultValueObj[field] formModel[field] = defaultValueObj![field]
}) })
} }
formModel[key] = getDefaultValue(schema, defaultValueRef, key) formModel[key] = getDefaultValue(schema, defaultValueRef, key)
@ -127,7 +127,7 @@ export function useFormEvents({
value = handleInputNumberValue(schema?.component, value) value = handleInputNumberValue(schema?.component, value)
const { componentProps } = schema || {} const { componentProps } = schema || {}
let _props = componentProps let _props = componentProps as any
if (typeof componentProps === 'function') if (typeof componentProps === 'function')
_props = _props({ formModel: unref(formModel) }) _props = _props({ formModel: unref(formModel) })
@ -230,18 +230,14 @@ export function useFormEvents({
} }
const index = schemaList.findIndex(schema => schema.field === prefixField) const index = schemaList.findIndex(schema => schema.field === prefixField)
const _schemaList = isObject(schema) ? [schema as FormSchema] : (schema as FormSchema[]) 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) 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 schemaRef.value = schemaList
_setDefaultValue(schema)
} }
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) { async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
@ -262,7 +258,7 @@ export function useFormEvents({
) )
return return
} }
schemaRef.value = updateData schemaRef.value = updateData as FormSchema[]
} }
async function updateSchema(data: Partial<FormSchema> | Partial<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 // Processing form values
function handleFormValues(values: Recordable) { function handleFormValues(values: Recordable) {
if (!isObject(values)) if (!isObject(values))
@ -73,6 +78,7 @@ export function useFormValues({ defaultValueRef, getSchema, formModel, getProps
// remove params from URL // remove params from URL
if (value === '') if (value === '')
value = undefined value = undefined
else else
value = value.trim() value = value.trim()
} }
@ -138,9 +144,9 @@ export function useFormValues({ defaultValueRef, getSchema, formModel, getProps
if (fieldKeys.length) { if (fieldKeys.length) {
// eslint-disable-next-line array-callback-return // eslint-disable-next-line array-callback-return
fieldKeys.map((field) => { fieldKeys.map((field) => {
obj[field] = defaultValueObj[field] obj[field] = defaultValueObj![field]
if (formModel[field] === undefined) if (formModel[field] === undefined)
formModel[field] = defaultValueObj[field] formModel[field] = defaultValueObj![field]
}) })
} }
if (!isNullOrUnDef(defaultValue)) { 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 { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
const { labelWidth, disabledLabelWidth } = schemaItem 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 labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { 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 { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
import type { RowProps } from 'ant-design-vue/lib/grid/Row' import type { RowProps } from 'ant-design-vue/lib/grid/Row'
import type { FieldMapToTime, FormSchema } from './types/form' import type { FieldMapToTime, FormSchema } from './types/form'
@ -41,7 +41,7 @@ export const basicProps = {
autoSubmitOnEnter: propTypes.bool.def(false), autoSubmitOnEnter: propTypes.bool.def(false),
submitOnReset: propTypes.bool, submitOnReset: propTypes.bool,
submitOnChange: propTypes.bool, submitOnChange: propTypes.bool,
size: propTypes.oneOf(['default', 'small', 'large']), size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
// 禁用表单 // 禁用表单
disabled: propTypes.bool, disabled: propTypes.bool,
emptySpan: { emptySpan: {

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

@ -33,9 +33,13 @@ export interface FormActionType {
resetSchema: (data: Partial<FormSchemaInner> | Partial<FormSchemaInner>[]) => Promise<void> resetSchema: (data: Partial<FormSchemaInner> | Partial<FormSchemaInner>[]) => Promise<void>
setProps: (formProps: Partial<FormProps>) => Promise<void> setProps: (formProps: Partial<FormProps>) => Promise<void>
removeSchemaByField: (field: string | string[]) => 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> 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> scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>
} }
@ -139,13 +143,25 @@ interface BaseFormSchema {
// Auxiliary text // Auxiliary text
subLabel?: string subLabel?: string
// Help text on the right side of the text // 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 // BaseHelp component props
helpComponentProps?: Partial<HelpComponentProps> helpComponentProps?: Partial<HelpComponentProps>
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid // Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
labelWidth?: string | number labelWidth?: string | number
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself // Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
disabledLabelWidth?: boolean disabledLabelWidth?: boolean
// Component parameters
componentProps?:
| ((opt: {
schema: FormSchema
tableAction: TableActionType
formActionType: FormActionType
formModel: Recordable
}) => Recordable)
| object
// Required // Required
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean) required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
@ -192,7 +208,11 @@ interface BaseFormSchema {
opts: RenderOpts, opts: RenderOpts,
) => VNode | VNode[] | string ) => 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 // Custom slot, similar to renderColContent
colSlot?: string colSlot?: string
@ -201,24 +221,14 @@ interface BaseFormSchema {
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[] dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]
} }
export interface ComponentFormSchema extends BaseFormSchema {
interface ComponentFormSchema extends BaseFormSchema {
// render component // render component
component: ComponentType 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 // Custom slot, in from-item
slot?: string slot: string
} }
export type FormSchema = ComponentFormSchema | SlotFormSchema 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 type { BasicTableProps, ColumnChangeParam, InnerHandlers, SizeType, TableActionType } from './types/table'
import HeaderCell from './components/HeaderCell.vue' import HeaderCell from './components/HeaderCell.vue'
import { usePagination } from './hooks/usePagination' import { usePagination } from './hooks/usePagination'
import { useColumns } from './hooks/useColumns' import { useColumns } from './hooks/useColumns'
import { useDataSource } from './hooks/useDataSource' import { useDataSource } from './hooks/useDataSource'
import { useLoading } from './hooks/useLoading' import { useLoading } from './hooks/useLoading'
@ -21,14 +22,16 @@ import { useTableFooter } from './hooks/useTableFooter'
import { useTableForm } from './hooks/useTableForm' import { useTableForm } from './hooks/useTableForm'
import { basicProps } from './props' import { basicProps } from './props'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { BasicForm, useForm } from '@/components/Form'
import { PageWrapperFixedHeightKey } from '@/enums/pageEnum' import { PageWrapperFixedHeightKey } from '@/enums/pageEnum'
import { BasicForm, useForm } from '@/components/Form'
import { isFunction } from '@/utils/is' import { isFunction } from '@/utils/is'
import { warn } from '@/utils/log' import { warn } from '@/utils/log'
defineOptions({ name: 'BasicTable' }) defineOptions({ name: 'BasicTable' })
const props = defineProps(basicProps) const props = defineProps(basicProps)
const emit = defineEmits([ const emit = defineEmits([
'fetch-success', 'fetch-success',
'fetch-error', 'fetch-error',
@ -47,11 +50,11 @@ const emit = defineEmits([
'change', 'change',
'columns-change', 'columns-change',
]) ])
const slots = useSlots() const slots = useSlots()
const attrs = useAttrs() const attrs = useAttrs()
const tableElRef = ref(null) const tableElRef = ref(null)
const tableData = ref<Recordable[]>([]) const tableData = ref([])
const wrapRef = ref(null) const wrapRef = ref(null)
const formRef = ref(null) const formRef = ref(null)
@ -72,7 +75,13 @@ watchEffect(() => {
}) })
const { getLoading, setLoading } = useLoading(getProps) const { getLoading, setLoading } = useLoading(getProps)
const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } = usePagination(getProps) const {
getPaginationInfo,
getPagination,
setPagination,
setShowPagination,
getShowPagination,
} = usePagination(getProps)
const { const {
getRowSelection, getRowSelection,
@ -121,10 +130,16 @@ function handleTableChange(pagination: any, filters: any, sorter: any, extra: an
onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra) onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra)
} }
const { getViewColumns, getColumns, setCacheColumnsByField, setCacheColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns( const {
getProps, getViewColumns,
getPaginationInfo, getColumns,
) setCacheColumnsByField,
setCacheColumns,
setColumnWidth,
setColumns,
getColumnsRef,
getCacheColumns,
} = useColumns(getProps, getPaginationInfo)
const { getScrollRef, redoHeight } = useTableScroll( const { getScrollRef, redoHeight } = useTableScroll(
getProps, getProps,
@ -148,7 +163,11 @@ const { customRow } = useCustomRow(getProps, {
const { getRowClassName } = useTableStyle(getProps, prefixCls) 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 = { const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => { onColumnsChange: (data: ColumnChangeParam[]) => {
@ -160,18 +179,19 @@ const handlers: InnerHandlers = {
const { getHeaderProps } = useTableHeader(getProps, slots, handlers) const { getHeaderProps } = useTableHeader(getProps, slots, handlers)
const { getFooterProps } = useTableFooter(getProps, getScrollRef, tableElRef, getDataSourceRef) const { getFooterProps } = useTableFooter(
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange, getShowForm, setShowForm } = useTableForm(
getProps, getProps,
slots, getScrollRef,
fetch, tableElRef,
getLoading, getDataSourceRef,
) )
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange, getShowForm, setShowForm }
= useTableForm(getProps, slots, fetch, getLoading)
const getBindValues = computed(() => { const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef) const dataSource = unref(getDataSourceRef)
let propsData: Recordable = { let propsData: any = {
...attrs, ...attrs,
customRow, customRow,
...unref(getProps), ...unref(getProps),
@ -219,10 +239,6 @@ function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props } innerPropsRef.value = { ...unref(innerPropsRef), ...props }
} }
function handleResizeColumn(w, col) {
col.width = w
}
const tableAction: TableActionType = { const tableAction: TableActionType = {
reload, reload,
getSelectRows, getSelectRows,
@ -265,7 +281,7 @@ const tableAction: TableActionType = {
} }
createTableContext({ ...tableAction, wrapRef, getBindValues }) createTableContext({ ...tableAction, wrapRef, getBindValues })
defineExpose({ tableAction }) defineExpose(tableAction)
emit('register', tableAction, formActions) emit('register', tableAction, formActions)
</script> </script>
@ -293,7 +309,7 @@ emit('register', tableAction, formActions)
v-bind="getBindValues" v-bind="getBindValues"
:row-class-name="getRowClassName" :row-class-name="getRowClassName"
@change="handleTableChange" @change="handleTableChange"
@resize-column="handleResizeColumn" @resize-column="setColumnWidth"
> >
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item"> <template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
<slot :name="item" v-bind="data || {}" /> <slot :name="item" v-bind="data || {}" />
@ -315,9 +331,10 @@ emit('register', tableAction, formActions)
</template> </template>
<style lang="less"> <style lang="less">
@border-color: #cecece4d;
@prefix-cls: ~'@{namespace}-basic-table'; @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:hover.ant-table-row-selected > td,
.ant-table-tbody > tr.ant-table-row-selected td { .ant-table-tbody > tr.ant-table-row-selected td {
background-color: #262626; background-color: #262626;
@ -378,7 +395,7 @@ html[data-theme='dark'] {
//} //}
} }
.ant-pagination { .ant-table-wrapper .ant-pagination {
margin: 10px 0 0; margin: 10px 0 0;
} }

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

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

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

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

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

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

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

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import { computed } 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 { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
@ -20,11 +20,12 @@ const props = defineProps({
srcPrefix: propTypes.string.def(''), srcPrefix: propTypes.string.def(''),
// fallback, // fallback,
fallback: propTypes.string.def( 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==', 'data:image/png;base64,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',
), ),
}) })
const PreviewGroup = Image.PreviewGroup
const getWrapStyle = computed((): CSSProperties => { const getWrapStyle = computed((): CSSProperties => {
const { size } = props const { size } = props
const s = `${size}px` const s = `${size}px`
@ -35,10 +36,15 @@ const { prefixCls } = useDesign('basic-table-img')
</script> </script>
<template> <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"> <Badge v-if="simpleShow" :count="!showBadge || imgList.length === 1 ? 0 : imgList.length">
<div class="img-div"> <div class="img-div">
<ImagePreviewGroup> <PreviewGroup>
<template v-for="(img, index) in imgList" :key="img"> <template v-for="(img, index) in imgList" :key="img">
<Image <Image
:width="size" :width="size"
@ -49,14 +55,19 @@ const { prefixCls } = useDesign('basic-table-img')
:fallback="fallback" :fallback="fallback"
/> />
</template> </template>
</ImagePreviewGroup> </PreviewGroup>
</div> </div>
</Badge> </Badge>
<ImagePreviewGroup v-else> <PreviewGroup v-else>
<template v-for="(img, index) in imgList" :key="img"> <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> </template>
</ImagePreviewGroup> </PreviewGroup>
</div> </div>
</template> </template>

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

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'
import { computed } from 'vue' import { computed } from 'vue'
import { BasicTitle } from '@/components/Basic' import { BasicTitle } from '@/components/Basic'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
@ -8,10 +9,10 @@ defineOptions({ name: 'BasicTableTitle' })
const props = defineProps({ const props = defineProps({
title: { title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>, type: [Function, String] as PropType<string | ((data) => string)>,
}, },
getSelectRows: { getSelectRows: {
type: Function as PropType<() => Recordable[]>, type: Function as PropType<() => any[]>,
}, },
helpMessage: { helpMessage: {
type: [String, Array] as PropType<string | string[]>, 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 --> <!-- eslint-disable vue/no-mutating-props -->
<script lang="tsx"> <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 { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue' import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue'
import { pick, set } from 'lodash-es' import { pick, set } from 'lodash-es'
@ -8,9 +8,8 @@ import { Spin } from 'ant-design-vue'
import type { BasicColumn } from '../../types/table' import type { BasicColumn } from '../../types/table'
import { useTableContext } from '../../hooks/useTableContext' import { useTableContext } from '../../hooks/useTableContext'
import { CellComponent } from './CellComponent' import { CellComponent } from './CellComponent'
import { createPlaceholderMessage } from './helper'
import type { EditRecordRow } from './index'
import { createPlaceholderMessage } from './helper'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import clickOutside from '@/directives/clickOutside' import clickOutside from '@/directives/clickOutside'
@ -27,11 +26,13 @@ export default defineComponent({
}, },
props: { props: {
value: { 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: '', default: '',
}, },
record: { record: {
type: Object as PropType<EditRecordRow>, type: Object as any,
}, },
column: { column: {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
@ -45,7 +46,7 @@ export default defineComponent({
const elRef = ref() const elRef = ref()
const ruleOpen = ref(false) const ruleOpen = ref(false)
const ruleMessage = ref('') const ruleMessage = ref('')
const optionsRef = ref<LabelValueOptions>([]) const optionsRef = ref([])
const currentValueRef = ref<any>(props.value) const currentValueRef = ref<any>(props.value)
const defaultValueRef = ref<any>(props.value) const defaultValueRef = ref<any>(props.value)
const spinning = ref<boolean>(false) const spinning = ref<boolean>(false)
@ -63,7 +64,6 @@ export default defineComponent({
const component = unref(getComponent) const component = unref(getComponent)
return ['Checkbox', 'Switch'].includes(component) return ['Checkbox', 'Switch'].includes(component)
}) })
const getDisable = computed(() => { const getDisable = computed(() => {
const { editDynamicDisabled } = props.column const { editDynamicDisabled } = props.column
let disabled = false let disabled = false
@ -85,7 +85,7 @@ export default defineComponent({
const value = isCheckValue ? (isNumber(val) || isBoolean(val) ? val : !!val) : val 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 const { record, column, index } = props
if (isFunction(compProps)) if (isFunction(compProps))
@ -96,7 +96,7 @@ export default defineComponent({
delete compProps.onChange delete compProps.onChange
const component = unref(getComponent) const component = unref(getComponent)
const apiSelectProps: Recordable = {} const apiSelectProps: Record<string, any> = {}
if (component === 'ApiSelect') if (component === 'ApiSelect')
apiSelectProps.cache = true apiSelectProps.cache = true
@ -133,12 +133,11 @@ export default defineComponent({
if (!component.includes('Select') && !component.includes('Radio')) if (!component.includes('Select') && !component.includes('Radio'))
return value 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}`) const option = options.find(item => `${item.value}` === `${value}`)
return option?.label ?? value return option?.label ?? value
}) })
const getRowEditable = computed(() => { const getRowEditable = computed(() => {
const { editable } = props.record || {} const { editable } = props.record || {}
return !!editable return !!editable
@ -184,12 +183,16 @@ export default defineComponent({
const component = unref(getComponent) const component = unref(getComponent)
if (!e) if (!e)
currentValueRef.value = e currentValueRef.value = e
else if (component === 'Checkbox') else if (component === 'Checkbox')
currentValueRef.value = (e as ChangeEvent).target.checked currentValueRef.value = e.target.checked
else if (component === 'Switch') else if (component === 'Switch')
currentValueRef.value = e currentValueRef.value = e
else if (e?.target && Reflect.has(e.target, 'value')) 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)) else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e))
currentValueRef.value = e currentValueRef.value = e
@ -219,7 +222,7 @@ export default defineComponent({
return false return false
} }
if (isFunction(editRule)) { if (isFunction(editRule)) {
const res = await editRule(currentValue, record as Recordable) const res = await editRule(currentValue, record)
if (res) { if (res) {
ruleMessage.value = res ruleMessage.value = res
ruleOpen.value = true ruleOpen.value = true
@ -259,7 +262,9 @@ export default defineComponent({
if (beforeEditSubmit && isFunction(beforeEditSubmit)) { if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
spinning.value = true 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 let result: any = true
try { try {
result = await beforeEditSubmit({ result = await beforeEditSubmit({
@ -322,28 +327,31 @@ export default defineComponent({
} }
// only ApiSelect or TreeSelect // only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) { function handleOptionsChange(options) {
const { replaceFields } = unref(getComponentProps) const { replaceFields } = unref(getComponentProps)
const component = unref(getComponent) const component = unref(getComponent)
if (component === 'ApiTreeSelect') { if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {} const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}
let listOptions: Recordable[] = treeToList(options, { children }) let listOptions = treeToList(options, { children })
listOptions = listOptions.map((item) => { listOptions = listOptions.map((item) => {
return { return {
label: item[title], label: item[title],
value: item[value], value: item[value],
} }
}) })
optionsRef.value = listOptions as LabelValueOptions optionsRef.value = listOptions
} }
else { else {
optionsRef.value = options optionsRef.value = options
} }
} }
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) { function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle) {
if (props.record) if (props.record) {
isArray(props.record[cbs]) ? props.record[cbs]?.push(handle) : (props.record[cbs] = [handle]) isArray(props.record[cbs])
? props.record[cbs]?.push(handle)
: (props.record[cbs] = [handle])
}
} }
if (props.record) { if (props.record) {
@ -434,7 +442,10 @@ export default defineComponent({
/> />
{!this.getRowEditable && ( {!this.getRowEditable && (
<div class={`${this.prefixCls}__action`}> <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} /> <CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
</div> </div>
)} )}
@ -477,10 +488,12 @@ export default defineComponent({
.edit-cell-rule-popover { .edit-cell-rule-popover {
.ant-popover-inner-content { .ant-popover-inner-content {
padding: 4px 8px; padding: 4px 8px;
color: @error-color;
// border: 1px solid @error-color; // border: 1px solid @error-color;
border-radius: 2px; border-radius: 2px;
} }
} }
.@{prefix-cls} { .@{prefix-cls} {
position: relative; position: relative;
min-height: 24px; // hover min-height: 24px; // hover
@ -490,7 +503,7 @@ export default defineComponent({
align-items: center; align-items: center;
justify-content: center; justify-content: center;
> .ant-select { >.ant-select {
min-width: calc(100% - 50px); min-width: calc(100% - 50px);
} }
} }
@ -498,6 +511,10 @@ export default defineComponent({
&__icon { &__icon {
&:hover { &:hover {
transform: scale(1.2); 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 { isFunction, isNullAndUnDef } from '@/utils/is'
import { getPopupContainer as getParentContainer } from '@/utils' import { getPopupContainer as getParentContainer } from '@/utils'
defineOptions({ name: 'ColumnSetting' })
const emit = defineEmits(['columns-change'])
interface State { interface State {
checkAll: boolean checkAll: boolean
isInit?: boolean isInit?: boolean
@ -33,6 +29,10 @@ interface Options {
fixed?: boolean | 'left' | 'right' fixed?: boolean | 'left' | 'right'
} }
defineOptions({ name: 'ColumnSetting' })
const emit = defineEmits(['columns-change'])
const CheckboxGroup = Checkbox.Group const CheckboxGroup = Checkbox.Group
const attrs = useAttrs() const attrs = useAttrs()
@ -51,7 +51,7 @@ const plainOptions = ref<Options[] | any>([])
const plainSortOptions = ref<Options[]>([]) const plainSortOptions = ref<Options[]>([])
const columnListRef = ref<ComponentRef>(null) const columnListRef = ref(null)
const state = reactive<State>({ const state = reactive<State>({
checkAll: true, checkAll: true,
@ -124,7 +124,6 @@ async function init(isReset = false) {
return item.dataIndex || item.title return item.dataIndex || item.title
}) })
.filter(Boolean) as string[] .filter(Boolean) as string[]
plainOptions.value = columns plainOptions.value = columns
plainSortOptions.value = columns plainSortOptions.value = columns
// //
@ -136,13 +135,14 @@ async function init(isReset = false) {
state.checkAll = checkList.length === columns.length state.checkAll = checkList.length === columns.length
inited = false inited = false
handleOpenChange() handleOpenChange()
state.checkedList = checkList
} }
// checkAll change // checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) { function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainSortOptions.value.map(item => item.value) 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) { if (e.target.checked) {
state.checkedList = checkList state.checkedList = checkList
setColumns(checkList) setColumns(checkList)
@ -169,7 +169,7 @@ function onChange(checkedList: string[]) {
return sortList.indexOf(prev) - sortList.indexOf(next) return sortList.indexOf(prev) - sortList.indexOf(next)
}) })
unref(plainSortOptions).forEach((item) => { unref(plainSortOptions).forEach((item) => {
;(item as BasicColumn).defaultHidden = !checkedList.includes(item.value) (item as BasicColumn).defaultHidden = !checkedList.includes(item.value)
}) })
setColumns(checkedList) setColumns(checkedList)
} }
@ -197,7 +197,7 @@ function handleOpenChange() {
const columnListEl = unref(columnListRef) const columnListEl = unref(columnListRef)
if (!columnListEl) if (!columnListEl)
return return
const el = columnListEl.$el as any const el = (columnListEl as any).$el
if (!el) if (!el)
return return
// Drag and drop sort // Drag and drop sort
@ -256,7 +256,9 @@ function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string)) if (!state.checkedList.includes(item.dataIndex as string))
return 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 isFixed = item.fixed === fixed ? false : fixed
const index = columns.findIndex(col => col.dataIndex === item.dataIndex) const index = columns.findIndex(col => col.dataIndex === item.dataIndex)
if (index !== -1) if (index !== -1)
@ -278,7 +280,10 @@ function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns) table.setColumns(columns)
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => { const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const open 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 } return { dataIndex: col.value, fixed: col.fixed, open }
}) })
@ -286,7 +291,9 @@ function setColumns(columns: BasicColumn[] | string[]) {
} }
function getPopupContainer() { function getPopupContainer() {
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer() return isFunction(attrs.getPopupContainer)
? attrs.getPopupContainer()
: getParentContainer()
} }
function updateSortOption(column: BasicColumn) { function updateSortOption(column: BasicColumn) {
@ -406,6 +413,10 @@ function updateSortOption(column: BasicColumn) {
.ant-checkbox-wrapper { .ant-checkbox-wrapper {
width: 100%; width: 100%;
&:hover {
color: @primary-color;
}
} }
} }
@ -414,6 +425,11 @@ function updateSortOption(column: BasicColumn) {
color: rgb(0 0 0 / 45%); color: rgb(0 0 0 / 45%);
cursor: pointer; cursor: pointer;
&.active,
&:hover {
color: @primary-color;
}
&.disabled { &.disabled {
color: @disabled-color; color: @disabled-color;
cursor: not-allowed; cursor: not-allowed;

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

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

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

@ -2,7 +2,14 @@ import componentSetting from '@/settings/componentSetting'
const { table } = 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' 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[]> const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>
let cacheColumns = unref(propsRef).columns let cacheColumns = unref(propsRef).columns

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

@ -13,7 +13,11 @@ interface Options {
getAutoCreateKey: ComputedRef<boolean | undefined> 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) if (!rowKey || autoCreateKey)
return record[ROW_KEY] return record[ROW_KEY]
@ -40,13 +44,15 @@ export function useCustomRow(
return return
const keys = getSelectRowKeys() || [] const keys = getSelectRowKeys() || []
const key = getKey(record, rowKey, unref(getAutoCreateKey)) const key = getKey(record, rowKey, unref(getAutoCreateKey))
if (!key) if (key === null)
return return
const isCheckbox = rowSelection.type === 'checkbox' const isCheckbox = rowSelection.type === 'checkbox'
if (isCheckbox) { if (isCheckbox) {
// 找到tr // 找到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) if (!tr)
return return
// 找到Checkbox,检查是否为disabled // 找到Checkbox,检查是否为disabled
@ -54,7 +60,8 @@ export function useCustomRow(
if (!checkBox || checkBox.hasAttribute('disabled')) if (!checkBox || checkBox.hasAttribute('disabled'))
return return
if (!keys.includes(key)) { if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]) keys.push(key)
setSelectedRowKeys(keys)
return return
} }
const keyIndex = keys.findIndex(item => item === key) 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 { computed, onMounted, reactive, ref, unref, watch, watchEffect } from 'vue'
import { useTimeoutFn } from '@vueuse/core' import { useTimeoutFn } from '@vueuse/core'
import { cloneDeep, get, merge } from 'lodash-es' import { cloneDeep, get, merge } from 'lodash-es'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import type { PaginationProps } from '../types/pagination' import type { PaginationProps } from '../types/pagination'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const' import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const'
import { buildUUID } from '@/utils/uuid'
import { isBoolean, isFunction, isObject } from '@/utils/is' import { isBoolean, isFunction, isObject } from '@/utils/is'
import { buildUUID } from '@/utils/uuid'
interface ActionType { interface ActionType {
getPaginationInfo: ComputedRef<boolean | PaginationProps> getPaginationInfo: ComputedRef<boolean | PaginationProps>
@ -23,7 +23,14 @@ interface SearchState {
} }
export function useDataSource( export function useDataSource(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, getFieldsValue, clearSelectedRowKeys, tableData }: ActionType, {
getPaginationInfo,
setPagination,
setLoading,
getFieldsValue,
clearSelectedRowKeys,
tableData,
}: ActionType,
emit: EmitType, emit: EmitType,
) { ) {
const searchState = reactive<SearchState>({ 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) const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef)
if (clearSelectOnPageChange) if (clearSelectOnPageChange)
clearSelectedRowKeys() clearSelectedRowKeys()
@ -117,7 +128,7 @@ export function useDataSource(
return unref(dataSourceRef) 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] const record = dataSourceRef.value[index]
if (record) if (record)
dataSourceRef.value[index][key] = value dataSourceRef.value[index][key] = value
@ -125,7 +136,10 @@ export function useDataSource(
return dataSourceRef.value[index] 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) const row = findTableDataRecord(rowKey)
if (row) { 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; // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length index = index ?? dataSourceRef.value?.length
const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]) const _record = isObject(record) ? [record as Recordable] : (record as Recordable[])
@ -207,7 +224,7 @@ export function useDataSource(
let ret let ret
array.some(function iter(r) { array.some(function iter(r) {
if (typeof rowKeyName === 'function') { if (typeof rowKeyName === 'function') {
if ((rowKeyName(r)) === rowKey) { if ((rowKeyName(r) as string) === rowKey) {
ret = r ret = r
return true return true
} }
@ -234,12 +251,25 @@ export function useDataSource(
} }
async function fetch(opt?: FetchParams) { 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)) if (!api || !isFunction(api))
return return
try { try {
setLoading(true) 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 = {} let pageParams: Recordable = {}
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps 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[]) { function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values dataSourceRef.value = values as Recordable[]
} }
function getDataSource<T = 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) { function itemRender({ page, type, originalElement }: ItemRender) {
if (type === 'prev') if (type === 'prev')
return page === 0 ? null : <LeftOutlined /> return page === 0 ? null : <LeftOutlined />
else if (type === 'next') else if (type === 'next')
return page === 1 ? null : <RightOutlined /> return page === 1 ? null : <RightOutlined />
@ -76,7 +77,7 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
return unref(show) return unref(show)
} }
function setShowPagination(flag: boolean) { async function setShowPagination(flag: boolean) {
show.value = flag 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 { nextTick, unref } from 'vue'
import { warn } from '@/utils/log' 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 let bodyEl: HTMLElement | null
async function findTargetRowToScroll(targetRowData: Recordable) { async function findTargetRowToScroll(targetRowData: Recordable) {
const { id } = targetRowData 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 // Add a delay to get new dataSource
await nextTick() await nextTick()
bodyEl?.scrollTo({ bodyEl?.scrollTo({
@ -46,6 +51,7 @@ export function useTableScrollTo(tableElRef: Ref<ComponentRef>, getDataSourceRef
const targetRowData = dataSource.find(data => data.id === pos) const targetRowData = dataSource.find(data => data.id === pos)
if (targetRowData) if (targetRowData)
findTargetRowToScroll(targetRowData) findTargetRowToScroll(targetRowData)
else else
warn(`id: ${pos} doesn't exist`) warn(`id: ${pos} doesn't exist`)
} }

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

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

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

@ -20,7 +20,9 @@ export function useTableFooter(
const getFooterProps = computed((): Recordable | undefined => { const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary, summaryData } = unref(propsRef) 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(() => { watchEffect(() => {
@ -41,7 +43,9 @@ export function useTableFooter(
el: bodyDom, el: bodyDom,
name: 'scroll', name: 'scroll',
listener: () => { 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) if (!footerBodyDom || !bodyDom)
return return
footerBodyDom.scrollLeft = bodyDom.scrollLeft 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 getFormSlotKeys: ComputedRef<string[]> = computed(() => {
const keys = Object.keys(slots) 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) { function replaceFormSlotKey(key: string) {
if (!key) if (!key)
return '' return ''
return key?.replace?.(/form\-/, '') ?? '' return key?.replace?.(/form-/, '') ?? ''
} }
function handleSearchInfoChange(info: Recordable) { 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 { isString } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper' 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 getHeaderProps = computed((): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef) const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef)
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting 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) handleScrollBar(bodyEl, tableEl)
bodyEl.style.height = 'unset' bodyEl!.style.height = 'unset'
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0)
return return
@ -207,7 +207,7 @@ export function useTableScroll(
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height height = (height > maxHeight! ? (maxHeight as number) : height) ?? height
setHeight(height) setHeight(height)
bodyEl.style.height = `${height}px` bodyEl!.style.height = `${height}px`
} }
useWindowSizeFn(calcTableHeight, { wait: 280 }) useWindowSizeFn(calcTableHeight, { wait: 280 })
onMountedOrActivated(() => { 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 { 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 { DEFAULT_FILTER_FN, DEFAULT_SIZE, DEFAULT_SORT_FN, FETCH_SETTING } from './const'
import type { FormProps } from '@/components/Form' import type { FormProps } from '@/components/Form'
@ -126,7 +135,14 @@ export const basicProps = {
default: null, default: null,
}, },
beforeEditSubmit: { 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: { size: {
type: String as PropType<SizeType>, type: String as PropType<SizeType>,

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

@ -71,10 +71,13 @@ export interface ColumnProps<T> {
* Customized filter overlay * Customized filter overlay
* @type any (slot) * @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 * @type boolean
*/ */
filterDropdownOpen?: 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 * Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
* @type boolean | Function * @type boolean | Function
*/ */
sorter?: boolean | Fn sorter?: boolean | Function
/** /**
* Order of sorted values: 'ascend' 'descend' false * Order of sorted values: 'ascend' 'descend' false
@ -181,10 +184,10 @@ export interface ColumnProps<T> {
onFilter?: (value: any, record: T) => boolean 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 * @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, * 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 originalElement: any
} }
type PaginationPositon = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' type PaginationPositon =
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight'
export declare class PaginationConfig extends Pagination { export declare class PaginationConfig extends Pagination {
position?: PaginationPositon[] 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 { TableRowSelection as ITableRowSelection, Key } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table' import type { ColumnProps } from 'ant-design-vue/lib/table'
import type { PaginationProps } from './pagination' import type { PaginationProps } from './pagination'
@ -55,6 +55,11 @@ export interface ColumnFilterItem {
children?: any children?: any
} }
export interface TableCustomRecord<T = Recordable> {
record?: T
index?: number
}
export interface SorterResult { export interface SorterResult {
column: ColumnProps column: ColumnProps
order: SortOrder order: SortOrder
@ -89,7 +94,7 @@ export interface TableActionType {
getSelectRowKeys: () => Key[] getSelectRowKeys: () => Key[]
deleteSelectRowByKey: (key: string) => void deleteSelectRowByKey: (key: string) => void
setPagination: (info: Partial<PaginationProps>) => 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 updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => Recordable[] | void insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => Recordable[] | void
@ -108,7 +113,7 @@ export interface TableActionType {
getCacheColumns: () => BasicColumn[] getCacheColumns: () => BasicColumn[]
emit?: EmitType emit?: EmitType
updateTableData: (index: number, key: string, value: any) => Recordable updateTableData: (index: number, key: string, value: any) => Recordable
setShowPagination: (show: boolean) => void setShowPagination: (show: boolean) => Promise<void>
getShowPagination: () => boolean getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void
setCacheColumns?: (columns: BasicColumn[]) => void setCacheColumns?: (columns: BasicColumn[]) => void
@ -257,7 +262,7 @@ export interface BasicTableProps<T = any> {
* Customize row expand Icon. * Customize row expand Icon.
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
expandIcon?: Fn | VNodeChild | JSX.Element expandIcon?: Function | VNodeChild | JSX.Element
/** /**
* Whether to expand row by clicking anywhere in the whole row * Whether to expand row by clicking anywhere in the whole row
@ -275,7 +280,7 @@ export interface BasicTableProps<T = any> {
* Table footer renderer * Table footer renderer
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
footer?: Fn | VNodeChild | JSX.Element footer?: Function | VNodeChild | JSX.Element
/** /**
* Indent size in pixels of tree data * Indent size in pixels of tree data
@ -366,14 +371,19 @@ export interface BasicTableProps<T = any> {
* *
* @version 1.5.4 * @version 1.5.4
*/ */
transformCellText?: Fn transformCellText?: Function
/** /**
* Callback executed before editable cell submit value, not for row-editor * Callback executed before editable cell submit value, not for row-editor
* *
* The cell will not submit data while callback return false * 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 * Callback executed when pagination, filters or sorter is changed
@ -401,14 +411,19 @@ export interface BasicTableProps<T = any> {
onColumnsChange?: (data: ColumnChangeParam[]) => void 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> { export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[] children?: BasicColumn[]
filters?: { filters?: {
text: string text: string
value: 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渲染 // 自定义header渲染
customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element
// Whether to hide the column by default, it can be displayed in the column configuration // Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean defaultHidden?: boolean
@ -434,7 +448,12 @@ export interface BasicColumn extends ColumnProps<Recordable> {
editable?: boolean editable?: boolean
editComponent?: ComponentType editComponent?: ComponentType
editComponentProps?: 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 | Recordable
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>) editRule?: boolean | ((text: string, record: Recordable) => Promise<string>)
editValueMap?: (value: any) => string editValueMap?: (value: any) => string
@ -457,7 +476,7 @@ export interface BasicColumn extends ColumnProps<Recordable> {
export interface ColumnChangeParam { export interface ColumnChangeParam {
dataIndex: string dataIndex: string
fixed: boolean | 'left' | 'right' | undefined fixed: boolean | 'left' | 'right' | undefined
open: boolean visible: boolean
} }
export interface InnerHandlers { export interface InnerHandlers {

13
src/hooks/core/useAttrs.ts

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

22
src/hooks/core/useRefs.ts

@ -1,16 +1,24 @@
import type { Ref } from 'vue' import type { ComponentPublicInstance, Ref } from 'vue'
import { onBeforeUpdate, ref } from 'vue' import { onBeforeUpdate, shallowRef } from 'vue'
export function useRefs(): [Ref<HTMLElement[]>, (index: number) => (el: HTMLElement) => void] { function useRefs<T = HTMLElement>(): {
const refs = ref([]) as Ref<HTMLElement[]> refs: Ref<T[]>
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void
} {
const refs = shallowRef([]) as Ref<T[]>
onBeforeUpdate(() => { onBeforeUpdate(() => {
refs.value = [] refs.value = []
}) })
const setRefs = (index: number) => (el: HTMLElement) => { const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => {
refs.value[index] = el 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 { shallowRef, unref } from 'vue'
import { isFunction, isUnDef } from '@/utils/is'
export interface ScrollToParams { interface UseScrollToOptions {
el: any el: any
to: number to: number
duration?: number duration?: number
@ -16,6 +15,7 @@ function easeInOutQuad(t: number, b: number, c: number, d: number) {
t-- t--
return (-c / 2) * (t * (t - 2) - 1) + b return (-c / 2) * (t * (t - 2) - 1) + b
} }
function move(el: HTMLElement, amount: number) { function move(el: HTMLElement, amount: number) {
el.scrollTop = amount el.scrollTop = amount
} }
@ -23,13 +23,13 @@ function move(el: HTMLElement, amount: number) {
function position(el: HTMLElement) { function position(el: HTMLElement) {
return el.scrollTop 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 start = position(el)
const change = to - start const change = to - start
const increment = 20 const increment = 20
let currentTime = 0 let currentTime = 0
duration = isUnDef(duration) ? 500 : duration
const animateScroll = function () { const animateScroll = function () {
if (!unref(isActiveRef)) if (!unref(isActiveRef))
@ -42,7 +42,7 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
requestAnimationFrame(animateScroll) requestAnimationFrame(animateScroll)
} }
else { else {
if (callback && isFunction(callback)) if (callback && typeof callback === 'function')
callback() callback()
} }
} }
@ -57,3 +57,5 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
return { start: run, stop } 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 { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'
import type { AnyFunction } from '@/utils/types'
interface UseWindowSizeOptions { interface UseWindowSizeOptions {
wait?: number wait?: number
@ -7,7 +8,7 @@ interface UseWindowSizeOptions {
listenerOptions?: AddEventListenerOptions | boolean listenerOptions?: AddEventListenerOptions | boolean
} }
function useWindowSizeFn<T>(fn: Fn<T>, options: UseWindowSizeOptions = {}) { function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
const { wait = 150, immediate } = options const { wait = 150, immediate } = options
let handler = () => { let handler = () => {
fn() 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 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>> 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 Nullable<T> = T | null
/**
*
*/
export type Recordable<T = any> = Record<string, T>
export type RefElement = Nullable<HTMLElement> export type RefElement = Nullable<HTMLElement>
export type CustomizedHTMLElement<T> = HTMLElement & T export type CustomizedHTMLElement<T> = HTMLElement & T

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

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