new-map
parent
13f58ed486
commit
591a49805c
|
|
@ -1,17 +1,17 @@
|
|||
import BasicForm from './src/BasicForm.vue'
|
||||
import BasicForm from './src/BasicForm.vue';
|
||||
|
||||
export * from './src/types/form'
|
||||
export * from './src/types/formItem'
|
||||
export * from './src/types/form';
|
||||
export * from './src/types/formItem';
|
||||
|
||||
export { useComponentRegister } from './src/hooks/useComponentRegister'
|
||||
export { useForm } from './src/hooks/useForm'
|
||||
export { useComponentRegister } from './src/hooks/useComponentRegister';
|
||||
export { useForm } from './src/hooks/useForm';
|
||||
|
||||
export { default as ApiSelect } from './src/components/ApiSelect.vue'
|
||||
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'
|
||||
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'
|
||||
export { default as ApiTree } from './src/components/ApiTree.vue'
|
||||
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
|
||||
export { default as ApiCascader } from './src/components/ApiCascader.vue'
|
||||
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
|
||||
export { default as ApiSelect } from './src/components/ApiSelect.vue';
|
||||
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
|
||||
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
|
||||
export { default as ApiTree } from './src/components/ApiTree.vue';
|
||||
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
|
||||
export { default as ApiCascader } from './src/components/ApiCascader.vue';
|
||||
export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
|
||||
|
||||
export { BasicForm }
|
||||
export { BasicForm };
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<slot name="formHeader"></slot>
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<FormItem
|
||||
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
|
||||
:tableAction="tableAction"
|
||||
:formActionType="formActionType"
|
||||
:schema="schema"
|
||||
|
|
@ -118,9 +119,9 @@
|
|||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
|
||||
for (const schema of schemas) {
|
||||
const { defaultValue, component } = schema
|
||||
const { defaultValue, component, isHandleDateDefaultValue = true } = schema
|
||||
// handle date type
|
||||
if (defaultValue && dateItemType.includes(component)) {
|
||||
if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
|
||||
if (!Array.isArray(defaultValue)) {
|
||||
schema.defaultValue = dateUtil(defaultValue)
|
||||
} else {
|
||||
|
|
@ -141,7 +142,7 @@
|
|||
}
|
||||
})
|
||||
|
||||
const { handleToggleAdvanced } = useAdvanced({
|
||||
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
|
||||
advanceState,
|
||||
emit,
|
||||
getProps,
|
||||
|
|
@ -243,6 +244,7 @@
|
|||
|
||||
function setFormModel(key: string, value: any) {
|
||||
formModel[key] = value
|
||||
|
||||
const { validateTrigger } = unref(getBindValue)
|
||||
if (!validateTrigger || validateTrigger === 'change') {
|
||||
validateFields([key]).catch((_) => {})
|
||||
|
|
@ -299,6 +301,7 @@
|
|||
getFormActionBindProps: computed(
|
||||
(): Recordable => ({ ...getProps.value, ...advanceState }),
|
||||
),
|
||||
fieldsIsAdvancedMap,
|
||||
...formActionType,
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Component } from 'vue'
|
||||
import type { ComponentType } from './types/index'
|
||||
import type { Component } from 'vue';
|
||||
import type { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
* Component list, register here to setting it in the form
|
||||
|
|
@ -19,65 +19,65 @@ import {
|
|||
Slider,
|
||||
Rate,
|
||||
Divider,
|
||||
} from 'ant-design-vue'
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import ApiRadioGroup from './components/ApiRadioGroup.vue'
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue'
|
||||
import ApiSelect from './components/ApiSelect.vue'
|
||||
import ApiTree from './components/ApiTree.vue'
|
||||
import ApiTreeSelect from './components/ApiTreeSelect.vue'
|
||||
import ApiCascader from './components/ApiCascader.vue'
|
||||
import ApiTransfer from './components/ApiTransfer.vue'
|
||||
import { BasicUpload } from '/@/components/Upload'
|
||||
import { StrengthMeter } from '/@/components/StrengthMeter'
|
||||
import { IconPicker } from '/@/components/Icon'
|
||||
import { CountdownInput } from '/@/components/CountDown'
|
||||
import ApiRadioGroup from './components/ApiRadioGroup.vue';
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue';
|
||||
import ApiSelect from './components/ApiSelect.vue';
|
||||
import ApiTree from './components/ApiTree.vue';
|
||||
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
||||
import ApiCascader from './components/ApiCascader.vue';
|
||||
import ApiTransfer from './components/ApiTransfer.vue';
|
||||
import { BasicUpload } from '/@/components/Upload';
|
||||
import { StrengthMeter } from '/@/components/StrengthMeter';
|
||||
import { IconPicker } from '/@/components/Icon';
|
||||
import { CountdownInput } from '/@/components/CountDown';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>()
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('Input', Input)
|
||||
componentMap.set('InputGroup', Input.Group)
|
||||
componentMap.set('InputPassword', Input.Password)
|
||||
componentMap.set('InputSearch', Input.Search)
|
||||
componentMap.set('InputTextArea', Input.TextArea)
|
||||
componentMap.set('InputNumber', InputNumber)
|
||||
componentMap.set('AutoComplete', AutoComplete)
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputGroup', Input.Group);
|
||||
componentMap.set('InputPassword', Input.Password);
|
||||
componentMap.set('InputSearch', Input.Search);
|
||||
componentMap.set('InputTextArea', Input.TextArea);
|
||||
componentMap.set('InputNumber', InputNumber);
|
||||
componentMap.set('AutoComplete', AutoComplete);
|
||||
|
||||
componentMap.set('Select', Select)
|
||||
componentMap.set('ApiSelect', ApiSelect)
|
||||
componentMap.set('ApiTree', ApiTree)
|
||||
componentMap.set('TreeSelect', TreeSelect)
|
||||
componentMap.set('ApiTreeSelect', ApiTreeSelect)
|
||||
componentMap.set('ApiRadioGroup', ApiRadioGroup)
|
||||
componentMap.set('Switch', Switch)
|
||||
componentMap.set('RadioButtonGroup', RadioButtonGroup)
|
||||
componentMap.set('RadioGroup', Radio.Group)
|
||||
componentMap.set('Checkbox', Checkbox)
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group)
|
||||
componentMap.set('ApiCascader', ApiCascader)
|
||||
componentMap.set('Cascader', Cascader)
|
||||
componentMap.set('Slider', Slider)
|
||||
componentMap.set('Rate', Rate)
|
||||
componentMap.set('ApiTransfer', ApiTransfer)
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('ApiSelect', ApiSelect);
|
||||
componentMap.set('ApiTree', ApiTree);
|
||||
componentMap.set('TreeSelect', TreeSelect);
|
||||
componentMap.set('ApiTreeSelect', ApiTreeSelect);
|
||||
componentMap.set('ApiRadioGroup', ApiRadioGroup);
|
||||
componentMap.set('Switch', Switch);
|
||||
componentMap.set('RadioButtonGroup', RadioButtonGroup);
|
||||
componentMap.set('RadioGroup', Radio.Group);
|
||||
componentMap.set('Checkbox', Checkbox);
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group);
|
||||
componentMap.set('ApiCascader', ApiCascader);
|
||||
componentMap.set('Cascader', Cascader);
|
||||
componentMap.set('Slider', Slider);
|
||||
componentMap.set('Rate', Rate);
|
||||
componentMap.set('ApiTransfer', ApiTransfer);
|
||||
|
||||
componentMap.set('DatePicker', DatePicker)
|
||||
componentMap.set('MonthPicker', DatePicker.MonthPicker)
|
||||
componentMap.set('RangePicker', DatePicker.RangePicker)
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker)
|
||||
componentMap.set('TimePicker', TimePicker)
|
||||
componentMap.set('StrengthMeter', StrengthMeter)
|
||||
componentMap.set('IconPicker', IconPicker)
|
||||
componentMap.set('InputCountDown', CountdownInput)
|
||||
componentMap.set('DatePicker', DatePicker);
|
||||
componentMap.set('MonthPicker', DatePicker.MonthPicker);
|
||||
componentMap.set('RangePicker', DatePicker.RangePicker);
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker);
|
||||
componentMap.set('TimePicker', TimePicker);
|
||||
componentMap.set('StrengthMeter', StrengthMeter);
|
||||
componentMap.set('IconPicker', IconPicker);
|
||||
componentMap.set('InputCountDown', CountdownInput);
|
||||
|
||||
componentMap.set('Upload', BasicUpload)
|
||||
componentMap.set('Divider', Divider)
|
||||
componentMap.set('Upload', BasicUpload);
|
||||
componentMap.set('Divider', Divider);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component)
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName)
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
export { componentMap };
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@
|
|||
</a-cascader>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue'
|
||||
import { Cascader } from 'ant-design-vue'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue';
|
||||
import { Cascader } from 'ant-design-vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { get, omit } from 'lodash-es';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
loading?: boolean
|
||||
isLeaf?: boolean
|
||||
children?: Option[]
|
||||
value: string;
|
||||
label: string;
|
||||
loading?: boolean;
|
||||
isLeaf?: boolean;
|
||||
children?: Option[];
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'ApiCascader',
|
||||
|
|
@ -71,117 +71,117 @@
|
|||
},
|
||||
emits: ['change', 'defaultChange'],
|
||||
setup(props, { emit }) {
|
||||
const apiData = ref<any[]>([])
|
||||
const options = ref<Option[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const emitData = ref<any[]>([])
|
||||
const isFirstLoad = ref(true)
|
||||
const { t } = useI18n()
|
||||
const apiData = ref<any[]>([]);
|
||||
const options = ref<Option[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const emitData = ref<any[]>([]);
|
||||
const isFirstLoad = ref(true);
|
||||
const { t } = useI18n();
|
||||
// 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);
|
||||
|
||||
watch(
|
||||
apiData,
|
||||
(data) => {
|
||||
const opts = generatorOptions(data)
|
||||
options.value = opts
|
||||
const opts = generatorOptions(data);
|
||||
options.value = opts;
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
);
|
||||
|
||||
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) => {
|
||||
if (next) {
|
||||
const value = next[valueField]
|
||||
const value = next[valueField];
|
||||
const item = {
|
||||
...omit(next, [labelField, valueField]),
|
||||
label: next[labelField],
|
||||
value: numberToString ? `${value}` : value,
|
||||
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
|
||||
}
|
||||
const children = Reflect.get(next, childrenField)
|
||||
};
|
||||
const children = Reflect.get(next, childrenField);
|
||||
if (children) {
|
||||
Reflect.set(item, childrenField, generatorOptions(children))
|
||||
Reflect.set(item, childrenField, generatorOptions(children));
|
||||
}
|
||||
prev.push(item)
|
||||
prev.push(item);
|
||||
}
|
||||
return prev
|
||||
}, [] as Option[])
|
||||
return prev;
|
||||
}, [] as Option[]);
|
||||
}
|
||||
|
||||
async function initialFetch() {
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) return
|
||||
apiData.value = []
|
||||
loading.value = true
|
||||
const api = props.api;
|
||||
if (!api || !isFunction(api)) return;
|
||||
apiData.value = [];
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await api(props.initFetchParams)
|
||||
const res = await api(props.initFetchParams);
|
||||
if (Array.isArray(res)) {
|
||||
apiData.value = res
|
||||
return
|
||||
apiData.value = res;
|
||||
return;
|
||||
}
|
||||
if (props.resultField) {
|
||||
apiData.value = get(res, props.resultField) || []
|
||||
apiData.value = get(res, props.resultField) || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
console.warn(error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(selectedOptions: Option[]) {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1]
|
||||
targetOption.loading = true
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
targetOption.loading = true;
|
||||
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) return
|
||||
const api = props.api;
|
||||
if (!api || !isFunction(api)) return;
|
||||
try {
|
||||
const res = await api({
|
||||
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
|
||||
})
|
||||
});
|
||||
if (Array.isArray(res)) {
|
||||
const children = generatorOptions(res)
|
||||
targetOption.children = children
|
||||
return
|
||||
const children = generatorOptions(res);
|
||||
targetOption.children = children;
|
||||
return;
|
||||
}
|
||||
if (props.resultField) {
|
||||
const children = generatorOptions(get(res, props.resultField) || [])
|
||||
targetOption.children = children
|
||||
const children = generatorOptions(get(res, props.resultField) || []);
|
||||
targetOption.children = children;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
} finally {
|
||||
targetOption.loading = false
|
||||
targetOption.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && initialFetch()
|
||||
})
|
||||
props.immediate && initialFetch();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.initFetchParams,
|
||||
() => {
|
||||
!unref(isFirstLoad) && initialFetch()
|
||||
!unref(isFirstLoad) && initialFetch();
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
);
|
||||
|
||||
function handleChange(keys, args) {
|
||||
emitData.value = keys
|
||||
emit('defaultChange', keys, args)
|
||||
emitData.value = keys;
|
||||
emit('defaultChange', keys, args);
|
||||
}
|
||||
|
||||
function handleRenderDisplay({ labels, selectedOptions }) {
|
||||
if (unref(emitData).length === selectedOptions.length) {
|
||||
return labels.join(' / ')
|
||||
return labels.join(' / ');
|
||||
}
|
||||
if (props.displayRenderArray) {
|
||||
return props.displayRenderArray.join(' / ')
|
||||
return props.displayRenderArray.join(' / ');
|
||||
}
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
handleChange,
|
||||
loadData,
|
||||
handleRenderDisplay,
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
immediate: propTypes.bool.def(true),
|
||||
alwaysLoad: propTypes.bool.def(false),
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit }) {
|
||||
const options = ref<OptionsItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
|
@ -91,6 +91,13 @@
|
|||
props.immediate && !props.alwaysLoad && fetch()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.value,
|
||||
(v) => {
|
||||
emit('update:value', v)
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue'
|
||||
import { Transfer } from 'ant-design-vue'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
|
||||
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue';
|
||||
import { Transfer } from 'ant-design-vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { get, omit } from 'lodash-es';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
|
||||
export default defineComponent({
|
||||
name: 'ApiTransfer',
|
||||
components: { Transfer },
|
||||
|
|
@ -47,18 +47,18 @@
|
|||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const _dataSource = ref<TransferItem[]>([])
|
||||
const _targetKeys = ref<string[]>([])
|
||||
const { t } = useI18n()
|
||||
const _dataSource = ref<TransferItem[]>([]);
|
||||
const _targetKeys = ref<string[]>([]);
|
||||
const { t } = useI18n();
|
||||
|
||||
const getAttrs = computed(() => {
|
||||
return {
|
||||
...(!props.api ? { dataSource: unref(_dataSource) } : {}),
|
||||
...attrs,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
const getdataSource = computed(() => {
|
||||
const { labelField, valueField } = props
|
||||
const { labelField, valueField } = props;
|
||||
|
||||
return unref(_dataSource).reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
|
|
@ -66,69 +66,69 @@
|
|||
...omit(next, [labelField, valueField]),
|
||||
title: next[labelField],
|
||||
key: next[valueField],
|
||||
})
|
||||
});
|
||||
}
|
||||
return prev
|
||||
}, [] as TransferItem[])
|
||||
})
|
||||
return prev;
|
||||
}, [] as TransferItem[]);
|
||||
});
|
||||
const getTargetKeys = computed<string[]>(() => {
|
||||
if (unref(_targetKeys).length > 0) {
|
||||
return unref(_targetKeys)
|
||||
return unref(_targetKeys);
|
||||
}
|
||||
if (Array.isArray(props.value)) {
|
||||
return props.value
|
||||
return props.value;
|
||||
}
|
||||
return []
|
||||
})
|
||||
return [];
|
||||
});
|
||||
|
||||
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
|
||||
_targetKeys.value = keys
|
||||
console.log(direction)
|
||||
console.log(moveKeys)
|
||||
emit('change', keys)
|
||||
_targetKeys.value = keys;
|
||||
console.log(direction);
|
||||
console.log(moveKeys);
|
||||
emit('change', keys);
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && !props.alwaysLoad && fetch()
|
||||
})
|
||||
props.immediate && !props.alwaysLoad && fetch();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
fetch()
|
||||
fetch();
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
);
|
||||
|
||||
async function fetch() {
|
||||
const api = props.api
|
||||
const api = props.api;
|
||||
if (!api || !isFunction(api)) {
|
||||
if (Array.isArray(props.dataSource)) {
|
||||
_dataSource.value = props.dataSource
|
||||
_dataSource.value = props.dataSource;
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
_dataSource.value = []
|
||||
_dataSource.value = [];
|
||||
try {
|
||||
const res = await api(props.params)
|
||||
const res = await api(props.params);
|
||||
if (Array.isArray(res)) {
|
||||
_dataSource.value = res
|
||||
emitChange()
|
||||
return
|
||||
_dataSource.value = res;
|
||||
emitChange();
|
||||
return;
|
||||
}
|
||||
if (props.resultField) {
|
||||
_dataSource.value = get(res, props.resultField) || []
|
||||
_dataSource.value = get(res, props.resultField) || [];
|
||||
}
|
||||
emitChange()
|
||||
emitChange();
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
console.warn(error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
function emitChange() {
|
||||
emit('options-change', unref(getdataSource))
|
||||
emit('options-change', unref(getdataSource));
|
||||
}
|
||||
return { getTargetKeys, getdataSource, t, getAttrs, handleChange }
|
||||
return { getTargetKeys, getdataSource, t, getAttrs, handleChange };
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'
|
||||
import { Tree } from 'ant-design-vue'
|
||||
import { isArray, isFunction } from '/@/utils/is'
|
||||
import { get } from 'lodash-es'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
|
||||
import { Tree } from 'ant-design-vue';
|
||||
import { isArray, isFunction } from '/@/utils/is';
|
||||
import { get } from 'lodash-es';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
export default defineComponent({
|
||||
name: 'ApiTree',
|
||||
components: { ATree: Tree, LoadingOutlined },
|
||||
|
|
@ -28,63 +28,63 @@
|
|||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const treeData = ref<Recordable[]>([])
|
||||
const isFirstLoaded = ref<Boolean>(false)
|
||||
const loading = ref(false)
|
||||
const treeData = ref<Recordable[]>([]);
|
||||
const isFirstLoaded = ref<Boolean>(false);
|
||||
const loading = ref(false);
|
||||
const getAttrs = computed(() => {
|
||||
return {
|
||||
...(props.api ? { treeData: unref(treeData) } : {}),
|
||||
...attrs,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(...args) {
|
||||
emit('change', ...args)
|
||||
emit('change', ...args);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
!unref(isFirstLoaded) && fetch()
|
||||
!unref(isFirstLoaded) && fetch();
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.immediate,
|
||||
(v) => {
|
||||
v && !isFirstLoaded.value && fetch()
|
||||
v && !isFirstLoaded.value && fetch();
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
props.immediate && fetch()
|
||||
})
|
||||
props.immediate && fetch();
|
||||
});
|
||||
|
||||
async function fetch() {
|
||||
const { api, afterFetch } = props
|
||||
if (!api || !isFunction(api)) return
|
||||
loading.value = true
|
||||
treeData.value = []
|
||||
let result
|
||||
const { api, afterFetch } = props;
|
||||
if (!api || !isFunction(api)) return;
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result;
|
||||
try {
|
||||
result = await api(props.params)
|
||||
result = await api(props.params);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
if (afterFetch && isFunction(afterFetch)) {
|
||||
result = afterFetch(result)
|
||||
result = afterFetch(result);
|
||||
}
|
||||
loading.value = false
|
||||
if (!result) return
|
||||
loading.value = false;
|
||||
if (!result) return;
|
||||
if (!isArray(result)) {
|
||||
result = get(result, props.resultField)
|
||||
result = get(result, props.resultField);
|
||||
}
|
||||
treeData.value = (result as Recordable[]) || []
|
||||
isFirstLoaded.value = true
|
||||
emit('options-change', treeData.value)
|
||||
treeData.value = (result as Recordable[]) || [];
|
||||
isFirstLoaded.value = true;
|
||||
emit('options-change', treeData.value);
|
||||
}
|
||||
return { getAttrs, loading, handleChange }
|
||||
return { getAttrs, loading, handleChange };
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@
|
|||
formActionType: {
|
||||
type: Object as PropType<FormActionType>,
|
||||
},
|
||||
isAdvanced: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const { t } = useI18n()
|
||||
|
|
@ -103,8 +106,8 @@
|
|||
const { show, ifShow } = props.schema
|
||||
const { showAdvancedButton } = props.formProps
|
||||
const itemIsAdvanced = showAdvancedButton
|
||||
? isBoolean(props.schema.isAdvanced)
|
||||
? props.schema.isAdvanced
|
||||
? isBoolean(props.isAdvanced)
|
||||
? props.isAdvanced
|
||||
: true
|
||||
: true
|
||||
|
||||
|
|
@ -136,7 +139,6 @@
|
|||
dynamicRules,
|
||||
required,
|
||||
} = props.schema
|
||||
|
||||
if (isFunction(dynamicRules)) {
|
||||
return dynamicRules(unref(getValues)) as ValidationRule[]
|
||||
}
|
||||
|
|
@ -282,7 +284,6 @@
|
|||
...on,
|
||||
...bindValue,
|
||||
}
|
||||
|
||||
if (!renderComponentContent) {
|
||||
return <Comp {...compAttr} />
|
||||
}
|
||||
|
|
@ -291,6 +292,7 @@
|
|||
: {
|
||||
default: () => renderComponentContent,
|
||||
}
|
||||
|
||||
return <Comp {...compAttr}>{compSlot}</Comp>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
|
||||
import type { ComponentType } from './types/index'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { isNumber, isObject } from '/@/utils/is'
|
||||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
|
||||
import type { ComponentType } from './types/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { dateUtil } from '/@/utils/dateUtil';
|
||||
import { isNumber, isObject } from '/@/utils/is';
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n();
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component.includes('Input') || component.includes('Complete')) {
|
||||
return t('common.inputText')
|
||||
return t('common.inputText');
|
||||
}
|
||||
if (component.includes('Picker')) {
|
||||
return t('common.chooseText')
|
||||
return t('common.chooseText');
|
||||
}
|
||||
if (
|
||||
component.includes('Select') ||
|
||||
|
|
@ -24,15 +24,15 @@ export function createPlaceholderMessage(component: ComponentType) {
|
|||
component.includes('Switch')
|
||||
) {
|
||||
// return `请选择${label}`;
|
||||
return t('common.chooseText')
|
||||
return t('common.chooseText');
|
||||
}
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']
|
||||
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
|
||||
|
||||
function genType() {
|
||||
return [...DATE_TYPE, 'RangePicker']
|
||||
return [...DATE_TYPE, 'RangePicker'];
|
||||
}
|
||||
|
||||
export function setComponentRuleType(
|
||||
|
|
@ -41,34 +41,34 @@ export function setComponentRuleType(
|
|||
valueFormat: string,
|
||||
) {
|
||||
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)) {
|
||||
rule.type = 'array'
|
||||
rule.type = 'array';
|
||||
} else if (['InputNumber'].includes(component)) {
|
||||
rule.type = 'number'
|
||||
rule.type = 'number';
|
||||
}
|
||||
}
|
||||
|
||||
export function processDateValue(attr: Recordable, component: string) {
|
||||
const { valueFormat, value } = attr
|
||||
const { valueFormat, value } = attr;
|
||||
if (valueFormat) {
|
||||
attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value
|
||||
attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value;
|
||||
} else if (DATE_TYPE.includes(component) && value) {
|
||||
attr.value = dateUtil(attr.value)
|
||||
attr.value = dateUtil(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleInputNumberValue(component?: ComponentType, val?: any) {
|
||||
if (!component) return val
|
||||
if (!component) return val;
|
||||
if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
|
||||
return val && isNumber(val) ? `${val}` : val
|
||||
return val && isNumber(val) ? `${val}` : val;
|
||||
}
|
||||
return val
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
export const dateItemType = genType()
|
||||
export const dateItemType = genType();
|
||||
|
||||
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea']
|
||||
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea'];
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import type { ColEx } from '../types'
|
||||
import type { AdvanceState } from '../types/hooks'
|
||||
import { ComputedRef, getCurrentInstance, Ref } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { computed, unref, watch } from 'vue'
|
||||
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
|
||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import type { ColEx } from '../types';
|
||||
import type { AdvanceState } from '../types/hooks';
|
||||
import { ComputedRef, getCurrentInstance, Ref, shallowReactive } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { computed, unref, watch } from 'vue';
|
||||
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
|
||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
const BASIC_COL_LEN = 24
|
||||
const BASIC_COL_LEN = 24;
|
||||
|
||||
interface UseAdvancedContext {
|
||||
advanceState: AdvanceState
|
||||
emit: EmitType
|
||||
getProps: ComputedRef<FormProps>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
formModel: Recordable
|
||||
defaultValueRef: Ref<Recordable>
|
||||
advanceState: AdvanceState;
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
}
|
||||
|
||||
export default function ({
|
||||
|
|
@ -26,104 +26,106 @@ export default function ({
|
|||
formModel,
|
||||
defaultValueRef,
|
||||
}: UseAdvancedContext) {
|
||||
const vm = getCurrentInstance()
|
||||
const vm = getCurrentInstance();
|
||||
|
||||
const { realWidthRef, screenEnum, screenRef } = useBreakpoint()
|
||||
const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
|
||||
|
||||
const getEmptySpan = computed((): number => {
|
||||
if (!advanceState.isAdvanced) {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
// For some special cases, you need to manually specify additional blank lines
|
||||
const emptySpan = unref(getProps).emptySpan || 0
|
||||
const emptySpan = unref(getProps).emptySpan || 0;
|
||||
|
||||
if (isNumber(emptySpan)) {
|
||||
return emptySpan
|
||||
return emptySpan;
|
||||
}
|
||||
if (isObject(emptySpan)) {
|
||||
const { span = 0 } = emptySpan
|
||||
const screen = unref(screenRef) as string
|
||||
const { span = 0 } = emptySpan;
|
||||
const screen = unref(screenRef) as string;
|
||||
|
||||
const screenSpan = (emptySpan as any)[screen.toLowerCase()]
|
||||
return screenSpan || span || 0
|
||||
const screenSpan = (emptySpan as any)[screen.toLowerCase()];
|
||||
return screenSpan || span || 0;
|
||||
}
|
||||
return 0
|
||||
})
|
||||
return 0;
|
||||
});
|
||||
|
||||
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30)
|
||||
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30);
|
||||
|
||||
watch(
|
||||
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
|
||||
() => {
|
||||
const { showAdvancedButton } = unref(getProps)
|
||||
const { showAdvancedButton } = unref(getProps);
|
||||
if (showAdvancedButton) {
|
||||
debounceUpdateAdvanced()
|
||||
debounceUpdateAdvanced();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
);
|
||||
|
||||
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
|
||||
const width = unref(realWidthRef)
|
||||
const width = unref(realWidthRef);
|
||||
|
||||
const mdWidth =
|
||||
parseInt(itemCol.md as string) ||
|
||||
parseInt(itemCol.xs as string) ||
|
||||
parseInt(itemCol.sm as string) ||
|
||||
(itemCol.span as number) ||
|
||||
BASIC_COL_LEN
|
||||
BASIC_COL_LEN;
|
||||
|
||||
const lgWidth = parseInt(itemCol.lg as string) || mdWidth
|
||||
const xlWidth = parseInt(itemCol.xl as string) || lgWidth
|
||||
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth
|
||||
const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
|
||||
const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
|
||||
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
|
||||
if (width <= screenEnum.LG) {
|
||||
itemColSum += mdWidth
|
||||
itemColSum += mdWidth;
|
||||
} else if (width < screenEnum.XL) {
|
||||
itemColSum += lgWidth
|
||||
itemColSum += lgWidth;
|
||||
} else if (width < screenEnum.XXL) {
|
||||
itemColSum += xlWidth
|
||||
itemColSum += xlWidth;
|
||||
} else {
|
||||
itemColSum += xxlWidth
|
||||
itemColSum += xxlWidth;
|
||||
}
|
||||
|
||||
if (isLastAction) {
|
||||
advanceState.hideAdvanceBtn = false
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
if (itemColSum <= BASIC_COL_LEN * 2) {
|
||||
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed
|
||||
advanceState.hideAdvanceBtn = true
|
||||
advanceState.isAdvanced = true
|
||||
advanceState.hideAdvanceBtn = true;
|
||||
advanceState.isAdvanced = true;
|
||||
} 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
|
||||
} else if (!advanceState.isLoad) {
|
||||
advanceState.isLoad = true
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced
|
||||
advanceState.isLoad = true;
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum }
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
}
|
||||
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) {
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum }
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
} else {
|
||||
// The first line is always displayed
|
||||
return { isAdvanced: true, itemColSum }
|
||||
return { isAdvanced: true, itemColSum };
|
||||
}
|
||||
}
|
||||
|
||||
const fieldsIsAdvancedMap = shallowReactive({});
|
||||
|
||||
function updateAdvanced() {
|
||||
let itemColSum = 0
|
||||
let realItemColSum = 0
|
||||
const { baseColProps = {} } = unref(getProps)
|
||||
let itemColSum = 0;
|
||||
let realItemColSum = 0;
|
||||
const { baseColProps = {} } = unref(getProps);
|
||||
|
||||
for (const schema of unref(getSchema)) {
|
||||
const { show, colProps } = schema
|
||||
let isShow = true
|
||||
const { show, colProps } = schema;
|
||||
let isShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show
|
||||
isShow = show;
|
||||
}
|
||||
|
||||
if (isFunction(show)) {
|
||||
|
|
@ -135,36 +137,36 @@ export default function ({
|
|||
...unref(defaultValueRef),
|
||||
...formModel,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (isShow && (colProps || baseColProps)) {
|
||||
const { itemColSum: sum, isAdvanced } = getAdvanced(
|
||||
{ ...baseColProps, ...colProps },
|
||||
itemColSum,
|
||||
)
|
||||
);
|
||||
|
||||
itemColSum = sum || 0
|
||||
itemColSum = sum || 0;
|
||||
if (isAdvanced) {
|
||||
realItemColSum = itemColSum
|
||||
realItemColSum = itemColSum;
|
||||
}
|
||||
schema.isAdvanced = isAdvanced
|
||||
fieldsIsAdvancedMap[schema.field] = isAdvanced;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保页面发送更新
|
||||
vm?.proxy?.$forceUpdate()
|
||||
vm?.proxy?.$forceUpdate();
|
||||
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan)
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
|
||||
|
||||
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true)
|
||||
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);
|
||||
|
||||
emit('advanced-change')
|
||||
emit('advanced-change');
|
||||
}
|
||||
|
||||
function handleToggleAdvanced() {
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
|
||||
return { handleToggleAdvanced }
|
||||
return { handleToggleAdvanced, fieldsIsAdvancedMap };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,96 @@
|
|||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface'
|
||||
import type { DynamicProps } from '/#/utils'
|
||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue'
|
||||
import { isProdMode } from '/@/utils/env'
|
||||
import { error } from '/@/utils/log'
|
||||
import { getDynamicProps } from '/@/utils'
|
||||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import type { DynamicProps } from '/#/utils';
|
||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { error } from '/@/utils/log';
|
||||
import { getDynamicProps } from '/@/utils';
|
||||
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
|
||||
|
||||
type Props = Partial<DynamicProps<FormProps>>
|
||||
type Props = Partial<DynamicProps<FormProps>>;
|
||||
|
||||
export function useForm(props?: Props): UseFormReturnType {
|
||||
const formRef = ref<Nullable<FormActionType>>(null)
|
||||
const loadedRef = ref<Nullable<boolean>>(false)
|
||||
const formRef = ref<Nullable<FormActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
|
||||
async function getForm() {
|
||||
const form = unref(formRef)
|
||||
const form = unref(formRef);
|
||||
if (!form) {
|
||||
error(
|
||||
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
|
||||
)
|
||||
);
|
||||
}
|
||||
await nextTick()
|
||||
return form as FormActionType
|
||||
await nextTick();
|
||||
return form as FormActionType;
|
||||
}
|
||||
|
||||
function register(instance: FormActionType) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
formRef.value = null
|
||||
loadedRef.value = null
|
||||
})
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return
|
||||
formRef.value = null;
|
||||
loadedRef.value = null;
|
||||
});
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
|
||||
|
||||
formRef.value = instance
|
||||
loadedRef.value = true
|
||||
formRef.value = instance;
|
||||
loadedRef.value = true;
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
() => {
|
||||
props && instance.setProps(getDynamicProps(props))
|
||||
props && instance.setProps(getDynamicProps(props));
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
|
||||
const form = await getForm()
|
||||
form.scrollToField(name, options)
|
||||
const form = await getForm();
|
||||
form.scrollToField(name, options);
|
||||
},
|
||||
setProps: async (formProps: Partial<FormProps>) => {
|
||||
const form = await getForm()
|
||||
form.setProps(formProps)
|
||||
const form = await getForm();
|
||||
form.setProps(formProps);
|
||||
},
|
||||
|
||||
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm()
|
||||
form.updateSchema(data)
|
||||
const form = await getForm();
|
||||
form.updateSchema(data);
|
||||
},
|
||||
|
||||
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm()
|
||||
form.resetSchema(data)
|
||||
const form = await getForm();
|
||||
form.resetSchema(data);
|
||||
},
|
||||
|
||||
clearValidate: async (name?: string | string[]) => {
|
||||
const form = await getForm()
|
||||
form.clearValidate(name)
|
||||
const form = await getForm();
|
||||
form.clearValidate(name);
|
||||
},
|
||||
|
||||
resetFields: async () => {
|
||||
getForm().then(async (form) => {
|
||||
await form.resetFields()
|
||||
})
|
||||
await form.resetFields();
|
||||
});
|
||||
},
|
||||
|
||||
removeSchemaByField: async (field: string | string[]) => {
|
||||
unref(formRef)?.removeSchemaByField(field)
|
||||
unref(formRef)?.removeSchemaByField(field);
|
||||
},
|
||||
|
||||
// TODO promisify
|
||||
getFieldsValue: <T>() => {
|
||||
return unref(formRef)?.getFieldsValue() as T
|
||||
return unref(formRef)?.getFieldsValue() as T;
|
||||
},
|
||||
|
||||
setFieldsValue: async <T>(values: T) => {
|
||||
const form = await getForm()
|
||||
form.setFieldsValue<T>(values)
|
||||
const form = await getForm();
|
||||
form.setFieldsValue<T>(values);
|
||||
},
|
||||
|
||||
appendSchemaByField: async (
|
||||
|
|
@ -98,25 +98,25 @@ export function useForm(props?: Props): UseFormReturnType {
|
|||
prefixField: string | undefined,
|
||||
first: boolean,
|
||||
) => {
|
||||
const form = await getForm()
|
||||
form.appendSchemaByField(schema, prefixField, first)
|
||||
const form = await getForm();
|
||||
form.appendSchemaByField(schema, prefixField, first);
|
||||
},
|
||||
|
||||
submit: async (): Promise<any> => {
|
||||
const form = await getForm()
|
||||
return form.submit()
|
||||
const form = await getForm();
|
||||
return form.submit();
|
||||
},
|
||||
|
||||
validate: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm()
|
||||
return form.validate(nameList)
|
||||
const form = await getForm();
|
||||
return form.validate(nameList);
|
||||
},
|
||||
|
||||
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm()
|
||||
return form.validateFields(nameList)
|
||||
const form = await getForm();
|
||||
return form.validateFields(nameList);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
return [register, methods]
|
||||
return [register, methods];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { FormProps, FormSchema, FormActionType } from '../types/form'
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface'
|
||||
import { unref, toRaw, nextTick } from 'vue'
|
||||
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'
|
||||
import { deepMerge } from '/@/utils'
|
||||
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { cloneDeep, uniqBy } from 'lodash-es'
|
||||
import { error } from '/@/utils/log'
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormProps, FormSchema, FormActionType } from '../types/form';
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import { unref, toRaw, nextTick } from 'vue';
|
||||
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
|
||||
import { dateUtil } from '/@/utils/dateUtil';
|
||||
import { cloneDeep, uniqBy } from 'lodash-es';
|
||||
import { error } from '/@/utils/log';
|
||||
|
||||
interface UseFormActionContext {
|
||||
emit: EmitType
|
||||
getProps: ComputedRef<FormProps>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
formModel: Recordable
|
||||
defaultValueRef: Ref<Recordable>
|
||||
formElRef: Ref<FormActionType>
|
||||
schemaRef: Ref<FormSchema[]>
|
||||
handleFormValues: Fn
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
formElRef: Ref<FormActionType>;
|
||||
schemaRef: Ref<FormSchema[]>;
|
||||
handleFormValues: Fn;
|
||||
}
|
||||
export function useFormEvents({
|
||||
emit,
|
||||
|
|
@ -30,22 +30,22 @@ export function useFormEvents({
|
|||
handleFormValues,
|
||||
}: UseFormActionContext) {
|
||||
async function resetFields(): Promise<void> {
|
||||
const { resetFunc, submitOnReset } = unref(getProps)
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc())
|
||||
const { resetFunc, submitOnReset } = unref(getProps);
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc());
|
||||
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
|
||||
Object.keys(formModel).forEach((key) => {
|
||||
const schema = unref(getSchema).find((item) => item.field === key)
|
||||
const isInput = schema?.component && defaultValueComponents.includes(schema.component)
|
||||
const defaultValue = cloneDeep(defaultValueRef.value[key])
|
||||
formModel[key] = isInput ? defaultValue || '' : defaultValue
|
||||
})
|
||||
nextTick(() => clearValidate())
|
||||
const schema = unref(getSchema).find((item) => item.field === key);
|
||||
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
|
||||
const defaultValue = cloneDeep(defaultValueRef.value[key]);
|
||||
formModel[key] = isInput ? defaultValue || '' : defaultValue;
|
||||
});
|
||||
nextTick(() => clearValidate());
|
||||
|
||||
emit('reset', toRaw(formModel))
|
||||
submitOnReset && handleSubmit()
|
||||
emit('reset', toRaw(formModel));
|
||||
submitOnReset && handleSubmit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,78 +54,78 @@ export function useFormEvents({
|
|||
async function setFieldsValue(values: Recordable): Promise<void> {
|
||||
const fields = unref(getSchema)
|
||||
.map((item) => item.field)
|
||||
.filter(Boolean)
|
||||
.filter(Boolean);
|
||||
|
||||
// key 支持 a.b.c 的嵌套写法
|
||||
const delimiter = '.'
|
||||
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0)
|
||||
const delimiter = '.';
|
||||
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
|
||||
|
||||
const validKeys: string[] = []
|
||||
const validKeys: string[] = [];
|
||||
Object.keys(values).forEach((key) => {
|
||||
const schema = unref(getSchema).find((item) => item.field === key)
|
||||
let value = values[key]
|
||||
const schema = unref(getSchema).find((item) => item.field === key);
|
||||
let value = values[key];
|
||||
|
||||
const hasKey = Reflect.has(values, key)
|
||||
const hasKey = Reflect.has(values, key);
|
||||
|
||||
value = handleInputNumberValue(schema?.component, value)
|
||||
value = handleInputNumberValue(schema?.component, value);
|
||||
// 0| '' is allow
|
||||
if (hasKey && fields.includes(key)) {
|
||||
// time type
|
||||
if (itemIsDateType(key)) {
|
||||
if (Array.isArray(value)) {
|
||||
const arr: any[] = []
|
||||
const arr: any[] = [];
|
||||
for (const ele of value) {
|
||||
arr.push(ele ? dateUtil(ele) : null)
|
||||
arr.push(ele ? dateUtil(ele) : null);
|
||||
}
|
||||
formModel[key] = arr
|
||||
formModel[key] = arr;
|
||||
} else {
|
||||
const { componentProps } = schema || {}
|
||||
let _props = componentProps as any
|
||||
const { componentProps } = schema || {};
|
||||
let _props = componentProps as any;
|
||||
if (typeof componentProps === 'function') {
|
||||
_props = _props({ formModel })
|
||||
_props = _props({ formModel });
|
||||
}
|
||||
formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null
|
||||
formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null;
|
||||
}
|
||||
} else {
|
||||
formModel[key] = value
|
||||
formModel[key] = value;
|
||||
}
|
||||
validKeys.push(key)
|
||||
validKeys.push(key);
|
||||
} else {
|
||||
nestKeyArray.forEach((nestKey: string) => {
|
||||
try {
|
||||
const value = eval('values' + delimiter + nestKey)
|
||||
const value = eval('values' + delimiter + nestKey);
|
||||
if (isDef(value)) {
|
||||
formModel[nestKey] = value
|
||||
validKeys.push(nestKey)
|
||||
formModel[nestKey] = value;
|
||||
validKeys.push(nestKey);
|
||||
}
|
||||
} catch (e) {
|
||||
// key not exist
|
||||
if (isDef(defaultValueRef.value[nestKey])) {
|
||||
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey])
|
||||
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey]);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
validateFields(validKeys).catch((_) => {})
|
||||
});
|
||||
validateFields(validKeys).catch((_) => {});
|
||||
}
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
async function removeSchemaByField(fields: string | string[]): Promise<void> {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
if (!fields) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields;
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields]
|
||||
fieldList = [fields];
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
_removeSchemaByFeild(field, schemaList)
|
||||
_removeSchemaByFeild(field, schemaList);
|
||||
}
|
||||
schemaRef.value = schemaList
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -133,10 +133,10 @@ export function useFormEvents({
|
|||
*/
|
||||
function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void {
|
||||
if (isString(field)) {
|
||||
const index = schemaList.findIndex((schema) => schema.field === field)
|
||||
const index = schemaList.findIndex((schema) => schema.field === field);
|
||||
if (index !== -1) {
|
||||
delete formModel[field]
|
||||
schemaList.splice(index, 1)
|
||||
delete formModel[field];
|
||||
schemaList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,92 +145,92 @@ export function useFormEvents({
|
|||
* @description: Insert after a certain field, if not insert the last
|
||||
*/
|
||||
async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField)
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField);
|
||||
|
||||
if (!prefixField || index === -1 || first) {
|
||||
first ? schemaList.unshift(schema) : schemaList.push(schema)
|
||||
schemaRef.value = schemaList
|
||||
_setDefaultValue(schema)
|
||||
return
|
||||
first ? schemaList.unshift(schema) : schemaList.push(schema);
|
||||
schemaRef.value = schemaList;
|
||||
_setDefaultValue(schema);
|
||||
return;
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, schema)
|
||||
schemaList.splice(index + 1, 0, schema);
|
||||
}
|
||||
_setDefaultValue(schema)
|
||||
_setDefaultValue(schema);
|
||||
|
||||
schemaRef.value = schemaList
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = []
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema)
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data]
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every(
|
||||
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
|
||||
)
|
||||
);
|
||||
|
||||
if (!hasField) {
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
schemaRef.value = updateData as FormSchema[]
|
||||
schemaRef.value = updateData as FormSchema[];
|
||||
}
|
||||
|
||||
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = []
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema)
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data]
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every(
|
||||
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
|
||||
)
|
||||
);
|
||||
|
||||
if (!hasField) {
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
const schema: FormSchema[] = []
|
||||
const schema: FormSchema[] = [];
|
||||
updateData.forEach((item) => {
|
||||
unref(getSchema).forEach((val) => {
|
||||
if (val.field === item.field) {
|
||||
const newSchema = deepMerge(val, item)
|
||||
schema.push(newSchema as FormSchema)
|
||||
const newSchema = deepMerge(val, item);
|
||||
schema.push(newSchema as FormSchema);
|
||||
} else {
|
||||
schema.push(val)
|
||||
schema.push(val);
|
||||
}
|
||||
})
|
||||
})
|
||||
_setDefaultValue(schema)
|
||||
});
|
||||
});
|
||||
_setDefaultValue(schema);
|
||||
|
||||
schemaRef.value = uniqBy(schema, 'field')
|
||||
schemaRef.value = uniqBy(schema, 'field');
|
||||
}
|
||||
|
||||
function _setDefaultValue(data: FormSchema | FormSchema[]) {
|
||||
let schemas: FormSchema[] = []
|
||||
let schemas: FormSchema[] = [];
|
||||
if (isObject(data)) {
|
||||
schemas.push(data as FormSchema)
|
||||
schemas.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
schemas = [...data]
|
||||
schemas = [...data];
|
||||
}
|
||||
|
||||
const obj: Recordable = {}
|
||||
const currentFieldsValue = getFieldsValue()
|
||||
const obj: Recordable = {};
|
||||
const currentFieldsValue = getFieldsValue();
|
||||
schemas.forEach((item) => {
|
||||
if (
|
||||
item.component != 'Divider' &&
|
||||
|
|
@ -239,16 +239,16 @@ export function useFormEvents({
|
|||
!isNullOrUnDef(item.defaultValue) &&
|
||||
!(item.field in currentFieldsValue)
|
||||
) {
|
||||
obj[item.field] = item.defaultValue
|
||||
obj[item.field] = item.defaultValue;
|
||||
}
|
||||
})
|
||||
setFieldsValue(obj)
|
||||
});
|
||||
setFieldsValue(obj);
|
||||
}
|
||||
|
||||
function getFieldsValue(): Recordable {
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return {}
|
||||
return handleFormValues(toRaw(unref(formModel)))
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return {};
|
||||
return handleFormValues(toRaw(unref(formModel)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -256,44 +256,44 @@ export function useFormEvents({
|
|||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some((item) => {
|
||||
return item.field === key ? dateItemType.includes(item.component) : false
|
||||
})
|
||||
return item.field === key ? dateItemType.includes(item.component) : false;
|
||||
});
|
||||
}
|
||||
|
||||
async function validateFields(nameList?: NamePath[] | undefined) {
|
||||
return unref(formElRef)?.validateFields(nameList)
|
||||
return unref(formElRef)?.validateFields(nameList);
|
||||
}
|
||||
|
||||
async function validate(nameList?: NamePath[] | undefined) {
|
||||
return await unref(formElRef)?.validate(nameList)
|
||||
return await unref(formElRef)?.validate(nameList);
|
||||
}
|
||||
|
||||
async function clearValidate(name?: string | string[]) {
|
||||
await unref(formElRef)?.clearValidate(name)
|
||||
await unref(formElRef)?.clearValidate(name);
|
||||
}
|
||||
|
||||
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
|
||||
await unref(formElRef)?.scrollToField(name, options)
|
||||
await unref(formElRef)?.scrollToField(name, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Form submission
|
||||
*/
|
||||
async function handleSubmit(e?: Event): Promise<void> {
|
||||
e && e.preventDefault()
|
||||
const { submitFunc } = unref(getProps)
|
||||
e && e.preventDefault();
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc()
|
||||
return
|
||||
await submitFunc();
|
||||
return;
|
||||
}
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
try {
|
||||
const values = await validate()
|
||||
const res = handleFormValues(values)
|
||||
emit('submit', res)
|
||||
const values = await validate();
|
||||
const res = handleFormValues(values);
|
||||
emit('submit', res);
|
||||
} catch (error: any) {
|
||||
throw new Error(error)
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,5 +310,5 @@ export function useFormEvents({
|
|||
resetFields,
|
||||
setFieldsValue,
|
||||
scrollToField,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { unref } from 'vue'
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { cloneDeep, set } from 'lodash-es'
|
||||
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
|
||||
import { dateUtil } from '/@/utils/dateUtil';
|
||||
import { unref } from 'vue';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { cloneDeep, set } from 'lodash-es';
|
||||
|
||||
interface UseFormValuesContext {
|
||||
defaultValueRef: Ref<any>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
getProps: ComputedRef<FormProps>
|
||||
formModel: Recordable
|
||||
defaultValueRef: Ref<any>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
formModel: Recordable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription deconstruct array-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructArray(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\[(.+)\]$/
|
||||
const pattern = /^\[(.+)\]$/;
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern)
|
||||
const match = key.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',')
|
||||
value = Array.isArray(value) ? value : [value]
|
||||
const keys = match[1].split(',');
|
||||
value = Array.isArray(value) ? value : [value];
|
||||
keys.forEach((k, index) => {
|
||||
set(target, k.trim(), value[index])
|
||||
})
|
||||
return true
|
||||
set(target, k.trim(), value[index]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,16 +34,16 @@ function tryDeconstructArray(key: string, value: any, target: Recordable) {
|
|||
* @desription deconstruct object-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructObject(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\{(.+)\}$/
|
||||
const pattern = /^\{(.+)\}$/;
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern)
|
||||
const match = key.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',')
|
||||
value = isObject(value) ? value : {}
|
||||
const keys = match[1].split(',');
|
||||
value = isObject(value) ? value : {};
|
||||
keys.forEach((k) => {
|
||||
set(target, k.trim(), value[k.trim()])
|
||||
})
|
||||
return true
|
||||
set(target, k.trim(), value[k.trim()]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,75 +57,82 @@ export function useFormValues({
|
|||
// Processing form values
|
||||
function handleFormValues(values: Recordable) {
|
||||
if (!isObject(values)) {
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
const res: Recordable = {}
|
||||
const res: Recordable = {};
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item
|
||||
const [key] = item
|
||||
let [, value] = item;
|
||||
const [key] = item;
|
||||
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
const transformDateFunc = unref(getProps).transformDateFunc
|
||||
const transformDateFunc = unref(getProps).transformDateFunc;
|
||||
if (isObject(value)) {
|
||||
value = transformDateFunc?.(value)
|
||||
value = transformDateFunc?.(value);
|
||||
}
|
||||
|
||||
if (isArray(value) && value[0]?.format && value[1]?.format) {
|
||||
value = value.map((item) => transformDateFunc?.(item))
|
||||
value = value.map((item) => transformDateFunc?.(item));
|
||||
}
|
||||
// Remove spaces
|
||||
if (isString(value)) {
|
||||
value = value.trim()
|
||||
value = value.trim();
|
||||
}
|
||||
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
|
||||
// 没有解构成功的,按原样赋值
|
||||
set(res, key, value)
|
||||
set(res, key, value);
|
||||
}
|
||||
}
|
||||
return handleRangeTimeValue(res)
|
||||
return handleRangeTimeValue(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Processing time interval parameters
|
||||
*/
|
||||
function handleRangeTimeValue(values: Recordable) {
|
||||
const fieldMapToTime = unref(getProps).fieldMapToTime
|
||||
const fieldMapToTime = unref(getProps).fieldMapToTime;
|
||||
|
||||
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
|
||||
return values
|
||||
return values;
|
||||
}
|
||||
|
||||
for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
|
||||
if (!field || !startTimeKey || !endTimeKey || !values[field]) {
|
||||
continue
|
||||
if (!field || !startTimeKey || !endTimeKey) {
|
||||
continue;
|
||||
}
|
||||
// If the value to be converted is empty, remove the field
|
||||
if (!values[field]) {
|
||||
Reflect.deleteProperty(values, field);
|
||||
continue;
|
||||
}
|
||||
|
||||
const [startTime, endTime]: string[] = values[field]
|
||||
const [startTime, endTime]: string[] = values[field];
|
||||
|
||||
values[startTimeKey] = dateUtil(startTime).format(format)
|
||||
values[endTimeKey] = dateUtil(endTime).format(format)
|
||||
Reflect.deleteProperty(values, field)
|
||||
const [startTimeFormat, endTimeFormat] = Array.isArray(format) ? format : [format, format];
|
||||
|
||||
values[startTimeKey] = dateUtil(startTime).format(startTimeFormat);
|
||||
values[endTimeKey] = dateUtil(endTime).format(endTimeFormat);
|
||||
Reflect.deleteProperty(values, field);
|
||||
}
|
||||
|
||||
return values
|
||||
return values;
|
||||
}
|
||||
|
||||
function initDefault() {
|
||||
const schemas = unref(getSchema)
|
||||
const obj: Recordable = {}
|
||||
const schemas = unref(getSchema);
|
||||
const obj: Recordable = {};
|
||||
schemas.forEach((item) => {
|
||||
const { defaultValue } = item
|
||||
const { defaultValue } = item;
|
||||
if (!isNullOrUnDef(defaultValue)) {
|
||||
obj[item.field] = defaultValue
|
||||
obj[item.field] = defaultValue;
|
||||
|
||||
if (formModel[item.field] === undefined) {
|
||||
formModel[item.field] = defaultValue
|
||||
formModel[item.field] = defaultValue;
|
||||
}
|
||||
}
|
||||
})
|
||||
defaultValueRef.value = cloneDeep(obj)
|
||||
});
|
||||
defaultValueRef.value = cloneDeep(obj);
|
||||
}
|
||||
|
||||
return { handleFormValues, initDefault }
|
||||
return { handleFormValues, initDefault };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import type { Ref } from 'vue'
|
||||
import { computed, unref } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { isNumber } from '/@/utils/is'
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, unref } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { isNumber } from '/@/utils/is';
|
||||
|
||||
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
||||
return computed(() => {
|
||||
const schemaItem = unref(schemaItemRef)
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem
|
||||
const schemaItem = unref(schemaItemRef);
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem;
|
||||
|
||||
const {
|
||||
labelWidth: globalLabelWidth,
|
||||
labelCol: globalLabelCol,
|
||||
wrapperCol: globWrapperCol,
|
||||
layout,
|
||||
} = unref(propsRef)
|
||||
} = unref(propsRef);
|
||||
|
||||
// If labelWidth is set globally, all items setting
|
||||
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
|
||||
labelCol.style = {
|
||||
textAlign: 'left',
|
||||
}
|
||||
return { labelCol, wrapperCol }
|
||||
};
|
||||
return { labelCol, wrapperCol };
|
||||
}
|
||||
let width = labelWidth || globalLabelWidth
|
||||
const col = { ...globalLabelCol, ...labelCol }
|
||||
const wrapCol = { ...globWrapperCol, ...wrapperCol }
|
||||
let width = labelWidth || globalLabelWidth;
|
||||
const col = { ...globalLabelCol, ...labelCol };
|
||||
const wrapCol = { ...globWrapperCol, ...wrapperCol };
|
||||
|
||||
if (width) {
|
||||
width = isNumber(width) ? `${width}px` : width
|
||||
width = isNumber(width) ? `${width}px` : width;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -37,6 +37,6 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
|
|||
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
|
||||
...wrapCol,
|
||||
},
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import type { FieldMapToTime, FormSchema } from './types/form'
|
||||
import type { CSSProperties, PropType } from 'vue'
|
||||
import type { ColEx } from './types'
|
||||
import type { TableActionType } from '/@/components/Table'
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import type { FieldMapToTime, FormSchema } from './types/form';
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { ColEx } from './types';
|
||||
import type { TableActionType } from '/@/components/Table';
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export const basicProps = {
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
// 标签宽度 固定宽度
|
||||
labelWidth: {
|
||||
|
|
@ -54,7 +54,7 @@ export const basicProps = {
|
|||
transformDateFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: (date: any) => {
|
||||
return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date
|
||||
return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date;
|
||||
},
|
||||
},
|
||||
rulesMessageJoinLabel: propTypes.bool.def(true),
|
||||
|
|
@ -100,4 +100,4 @@ export const basicProps = {
|
|||
labelAlign: propTypes.string,
|
||||
|
||||
rowProps: Object as PropType<RowProps>,
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,223 +1,227 @@
|
|||
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'
|
||||
import type { VNode } from 'vue'
|
||||
import type { ButtonProps as AntdButtonProps } from '/@/components/Button'
|
||||
import type { FormItem } from './formItem'
|
||||
import type { ColEx, ComponentType } from './index'
|
||||
import type { TableActionType } from '/@/components/Table/src/types/table'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
|
||||
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
|
||||
import type { VNode } from 'vue';
|
||||
import type { ButtonProps as AntdButtonProps } from '/@/components/Button';
|
||||
import type { FormItem } from './formItem';
|
||||
import type { ColEx, ComponentType } from './index';
|
||||
import type { TableActionType } from '/@/components/Table/src/types/table';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||
|
||||
export type FieldMapToTime = [string, [string, string], string?][]
|
||||
export type FieldMapToTime = [string, [string, string], (string | [string, string])?][];
|
||||
|
||||
export type Rule = RuleObject & {
|
||||
trigger?: 'blur' | 'change' | ['change', 'blur']
|
||||
}
|
||||
trigger?: 'blur' | 'change' | ['change', 'blur'];
|
||||
};
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema
|
||||
values: Recordable
|
||||
model: Recordable
|
||||
field: string
|
||||
schema: FormSchema;
|
||||
values: Recordable;
|
||||
model: Recordable;
|
||||
field: string;
|
||||
}
|
||||
|
||||
export interface ButtonProps extends AntdButtonProps {
|
||||
text?: string
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<void>
|
||||
setFieldsValue: <T>(values: T) => Promise<void>
|
||||
resetFields: () => Promise<void>
|
||||
getFieldsValue: () => Recordable
|
||||
clearValidate: (name?: string | string[]) => Promise<void>
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
|
||||
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>
|
||||
removeSchemaByField: (field: string | string[]) => Promise<void>
|
||||
submit: () => Promise<void>;
|
||||
setFieldsValue: <T>(values: T) => Promise<void>;
|
||||
resetFields: () => Promise<void>;
|
||||
getFieldsValue: () => Recordable;
|
||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
removeSchemaByField: (field: string | string[]) => Promise<void>;
|
||||
appendSchemaByField: (
|
||||
schema: FormSchema,
|
||||
prefixField: string | undefined,
|
||||
first?: boolean | undefined,
|
||||
) => Promise<void>
|
||||
validateFields: (nameList?: NamePath[]) => Promise<any>
|
||||
validate: (nameList?: NamePath[]) => Promise<any>
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>
|
||||
) => Promise<void>;
|
||||
validateFields: (nameList?: NamePath[]) => Promise<any>;
|
||||
validate: (nameList?: NamePath[]) => Promise<any>;
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType]
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
|
||||
export interface FormProps {
|
||||
name?: string
|
||||
layout?: 'vertical' | 'inline' | 'horizontal'
|
||||
name?: string;
|
||||
layout?: 'vertical' | 'inline' | 'horizontal';
|
||||
// Form value
|
||||
model?: Recordable
|
||||
model?: Recordable;
|
||||
// The width of all items in the entire form
|
||||
labelWidth?: number | string
|
||||
labelWidth?: number | string;
|
||||
// alignment
|
||||
labelAlign?: 'left' | 'right'
|
||||
labelAlign?: 'left' | 'right';
|
||||
// Row configuration for the entire form
|
||||
rowProps?: RowProps
|
||||
rowProps?: RowProps;
|
||||
// Submit form on reset
|
||||
submitOnReset?: boolean
|
||||
submitOnReset?: boolean;
|
||||
// Submit form on form changing
|
||||
submitOnChange?: boolean
|
||||
submitOnChange?: boolean;
|
||||
// Col configuration for the entire form
|
||||
labelCol?: Partial<ColEx>
|
||||
labelCol?: Partial<ColEx>;
|
||||
// Col configuration for the entire form
|
||||
wrapperCol?: Partial<ColEx>
|
||||
wrapperCol?: Partial<ColEx>;
|
||||
|
||||
// General row style
|
||||
baseRowStyle?: CSSProperties
|
||||
baseRowStyle?: CSSProperties;
|
||||
|
||||
// General col configuration
|
||||
baseColProps?: Partial<ColEx>
|
||||
baseColProps?: Partial<ColEx>;
|
||||
|
||||
// Form configuration rules
|
||||
schemas?: FormSchema[]
|
||||
schemas?: FormSchema[];
|
||||
// Function values used to merge into dynamic control form items
|
||||
mergeDynamicData?: Recordable
|
||||
mergeDynamicData?: Recordable;
|
||||
// Compact mode for search forms
|
||||
compact?: boolean
|
||||
compact?: boolean;
|
||||
// Blank line span
|
||||
emptySpan?: number | Partial<ColEx>
|
||||
emptySpan?: number | Partial<ColEx>;
|
||||
// Internal component size of the form
|
||||
size?: 'default' | 'small' | 'large'
|
||||
size?: 'default' | 'small' | 'large';
|
||||
// Whether to disable
|
||||
disabled?: boolean
|
||||
disabled?: boolean;
|
||||
// Time interval fields are mapped into multiple
|
||||
fieldMapToTime?: FieldMapToTime
|
||||
fieldMapToTime?: FieldMapToTime;
|
||||
// Placeholder is set automatically
|
||||
autoSetPlaceHolder?: boolean
|
||||
autoSetPlaceHolder?: boolean;
|
||||
// Auto submit on press enter on input
|
||||
autoSubmitOnEnter?: boolean
|
||||
autoSubmitOnEnter?: boolean;
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
// Whether to show collapse and expand buttons
|
||||
showAdvancedButton?: boolean
|
||||
showAdvancedButton?: boolean;
|
||||
// Whether to focus on the first input box, only works when the first form item is input
|
||||
autoFocusFirstItem?: boolean
|
||||
autoFocusFirstItem?: boolean;
|
||||
// Automatically collapse over the specified number of rows
|
||||
autoAdvancedLine?: number
|
||||
autoAdvancedLine?: number;
|
||||
// Always show lines
|
||||
alwaysShowLines?: number
|
||||
alwaysShowLines?: number;
|
||||
// Whether to show the operation button
|
||||
showActionButtonGroup?: boolean
|
||||
showActionButtonGroup?: boolean;
|
||||
|
||||
// Reset button configuration
|
||||
resetButtonOptions?: Partial<ButtonProps>
|
||||
resetButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// Confirm button configuration
|
||||
submitButtonOptions?: Partial<ButtonProps>
|
||||
submitButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// Operation column configuration
|
||||
actionColOptions?: Partial<ColEx>
|
||||
actionColOptions?: Partial<ColEx>;
|
||||
|
||||
// Show reset button
|
||||
showResetButton?: boolean
|
||||
showResetButton?: boolean;
|
||||
// Show confirmation button
|
||||
showSubmitButton?: boolean
|
||||
showSubmitButton?: boolean;
|
||||
|
||||
resetFunc?: () => Promise<void>
|
||||
submitFunc?: () => Promise<void>
|
||||
transformDateFunc?: (date: any) => string
|
||||
colon?: boolean
|
||||
resetFunc?: () => Promise<void>;
|
||||
submitFunc?: () => Promise<void>;
|
||||
transformDateFunc?: (date: any) => string;
|
||||
colon?: boolean;
|
||||
}
|
||||
export interface FormSchema {
|
||||
// Field name
|
||||
field: string
|
||||
field: string;
|
||||
// Event name triggered by internal value change, default change
|
||||
changeEvent?: string
|
||||
changeEvent?: string;
|
||||
// Variable name bound to v-model Default value
|
||||
valueField?: string
|
||||
valueField?: string;
|
||||
// Label name
|
||||
label: string | VNode
|
||||
label: string | VNode;
|
||||
// Auxiliary text
|
||||
subLabel?: string
|
||||
subLabel?: string;
|
||||
// Help text on the right side of the text
|
||||
helpMessage?:
|
||||
| string
|
||||
| string[]
|
||||
| ((renderCallbackParams: RenderCallbackParams) => string | string[])
|
||||
| ((renderCallbackParams: RenderCallbackParams) => string | string[]);
|
||||
// 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
|
||||
labelWidth?: string | number
|
||||
labelWidth?: string | number;
|
||||
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
|
||||
disabledLabelWidth?: boolean
|
||||
disabledLabelWidth?: boolean;
|
||||
// render component
|
||||
component: ComponentType
|
||||
component: ComponentType;
|
||||
// Component parameters
|
||||
componentProps?:
|
||||
| ((opt: {
|
||||
schema: FormSchema
|
||||
tableAction: TableActionType
|
||||
formActionType: FormActionType
|
||||
formModel: Recordable
|
||||
schema: FormSchema;
|
||||
tableAction: TableActionType;
|
||||
formActionType: FormActionType;
|
||||
formModel: Recordable;
|
||||
}) => Recordable)
|
||||
| object
|
||||
| object;
|
||||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number)
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[]
|
||||
rules?: Rule[];
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>
|
||||
itemProps?: Partial<FormItem>;
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any
|
||||
isAdvanced?: boolean
|
||||
defaultValue?: any;
|
||||
|
||||
// 是否自动处理与时间相关组件的默认值
|
||||
isHandleDateDefaultValue?: boolean;
|
||||
|
||||
isAdvanced?: boolean;
|
||||
|
||||
// Matching details components
|
||||
span?: number
|
||||
span?: number;
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
// Render the content in the form-item tag
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
// Rendering col content requires outer wrapper form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
renderComponentContent?:
|
||||
| ((renderCallbackParams: RenderCallbackParams) => any)
|
||||
| VNode
|
||||
| VNode[]
|
||||
| string
|
||||
| string;
|
||||
|
||||
// Custom slot, in from-item
|
||||
slot?: string
|
||||
slot?: string;
|
||||
|
||||
// Custom slot, similar to renderColContent
|
||||
colSlot?: string
|
||||
colSlot?: string;
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string
|
||||
maxWidth: string;
|
||||
// Whether to display the serial number
|
||||
showIndex: boolean
|
||||
showIndex: boolean;
|
||||
// Text list
|
||||
text: any
|
||||
text: any;
|
||||
// colour
|
||||
color: string
|
||||
color: string;
|
||||
// font size
|
||||
fontSize: string
|
||||
icon: string
|
||||
absolute: boolean
|
||||
fontSize: string;
|
||||
icon: string;
|
||||
absolute: boolean;
|
||||
// Positioning
|
||||
position: any
|
||||
position: any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,83 @@
|
|||
type ColSpanType = number | string
|
||||
type ColSpanType = number | string;
|
||||
export interface ColEx {
|
||||
style?: any
|
||||
style?: any;
|
||||
/**
|
||||
* raster number of cells to occupy, 0 corresponds to display: none
|
||||
* @default none (0)
|
||||
* @type ColSpanType
|
||||
*/
|
||||
span?: ColSpanType
|
||||
span?: ColSpanType;
|
||||
|
||||
/**
|
||||
* raster order, used in flex layout mode
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
order?: ColSpanType
|
||||
order?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the layout fill of flex
|
||||
* @default none
|
||||
* @type ColSpanType
|
||||
*/
|
||||
flex?: ColSpanType
|
||||
flex?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells to offset Col from the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
offset?: ColSpanType
|
||||
offset?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the right
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
push?: ColSpanType
|
||||
push?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
pull?: ColSpanType
|
||||
pull?: ColSpanType;
|
||||
|
||||
/**
|
||||
* <576px and also default setting, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥576px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥768px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥992px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1200px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1600px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
}
|
||||
|
||||
export type ComponentType =
|
||||
|
|
@ -114,4 +114,4 @@ export type ComponentType =
|
|||
| 'Slider'
|
||||
| 'Rate'
|
||||
| 'Divider'
|
||||
| 'ApiTransfer'
|
||||
| 'ApiTransfer';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import BasicForm from './src/BasicForm.vue'
|
||||
|
||||
export * from './src/types/form'
|
||||
export * from './src/types/formItem'
|
||||
|
||||
export { useComponentRegister } from './src/hooks/useComponentRegister'
|
||||
export { useForm } from './src/hooks/useForm'
|
||||
|
||||
export { default as ApiSelect } from './src/components/ApiSelect.vue'
|
||||
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'
|
||||
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'
|
||||
export { default as ApiTree } from './src/components/ApiTree.vue'
|
||||
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
|
||||
export { default as ApiCascader } from './src/components/ApiCascader.vue'
|
||||
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
|
||||
|
||||
export { BasicForm }
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
<template>
|
||||
<Form
|
||||
v-bind="getBindValue"
|
||||
:class="getFormClass"
|
||||
ref="formElRef"
|
||||
:model="formModel"
|
||||
@keypress.enter="handleEnterPress"
|
||||
>
|
||||
<Row v-bind="getRow">
|
||||
<slot name="formHeader"></slot>
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<FormItem
|
||||
:tableAction="tableAction"
|
||||
:formActionType="formActionType"
|
||||
:schema="schema"
|
||||
:formProps="getProps"
|
||||
:allDefaultValues="defaultValueRef"
|
||||
:formModel="formModel"
|
||||
:setFormModel="setFormModel"
|
||||
>
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
|
||||
<template
|
||||
#[item]="data"
|
||||
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
|
||||
>
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</FormAction>
|
||||
<slot name="formFooter"></slot>
|
||||
</Row>
|
||||
</Form>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { FormActionType, FormProps, FormSchema } from './types/form'
|
||||
import type { AdvanceState } from './types/hooks'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'
|
||||
import { Form, Row } from 'ant-design-vue'
|
||||
import FormItem from './components/FormItem.vue'
|
||||
import FormAction from './components/FormAction.vue'
|
||||
|
||||
import { dateItemType } from './helper'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
|
||||
// import { cloneDeep } from 'lodash-es';
|
||||
import { deepMerge } from '/@/utils'
|
||||
|
||||
import { useFormValues } from './hooks/useFormValues'
|
||||
import useAdvanced from './hooks/useAdvanced'
|
||||
import { useFormEvents } from './hooks/useFormEvents'
|
||||
import { createFormContext } from './hooks/useFormContext'
|
||||
import { useAutoFocus } from './hooks/useAutoFocus'
|
||||
import { useModalContext } from '/@/components/Modal'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
|
||||
import { basicProps } from './props'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicForm',
|
||||
components: { FormItem, Form, Row, FormAction },
|
||||
props: basicProps,
|
||||
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
|
||||
setup(props, { emit, attrs }) {
|
||||
const formModel = reactive<Recordable>({})
|
||||
const modalFn = useModalContext()
|
||||
|
||||
const advanceState = reactive<AdvanceState>({
|
||||
isAdvanced: true,
|
||||
hideAdvanceBtn: false,
|
||||
isLoad: false,
|
||||
actionSpan: 6,
|
||||
})
|
||||
|
||||
const defaultValueRef = ref<Recordable>({})
|
||||
const isInitedDefaultRef = ref(false)
|
||||
const propsRef = ref<Partial<FormProps>>({})
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null)
|
||||
const formElRef = ref<Nullable<FormActionType>>(null)
|
||||
|
||||
const { prefixCls } = useDesign('basic-form')
|
||||
|
||||
// Get the basic configuration of the form
|
||||
const getProps = computed((): FormProps => {
|
||||
return { ...props, ...unref(propsRef) } as FormProps
|
||||
})
|
||||
|
||||
const getFormClass = computed(() => {
|
||||
return [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}--compact`]: unref(getProps).compact,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// Get uniform row style and Row configuration for the entire form
|
||||
const getRow = computed((): Recordable => {
|
||||
const { baseRowStyle = {}, rowProps } = unref(getProps)
|
||||
return {
|
||||
style: baseRowStyle,
|
||||
...rowProps,
|
||||
}
|
||||
})
|
||||
|
||||
const getBindValue = computed(
|
||||
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
|
||||
)
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
|
||||
for (const schema of schemas) {
|
||||
const { defaultValue, component } = schema
|
||||
// handle date type
|
||||
if (defaultValue && dateItemType.includes(component)) {
|
||||
if (!Array.isArray(defaultValue)) {
|
||||
schema.defaultValue = dateUtil(defaultValue)
|
||||
} else {
|
||||
const def: any[] = []
|
||||
defaultValue.forEach((item) => {
|
||||
def.push(dateUtil(item))
|
||||
})
|
||||
schema.defaultValue = def
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unref(getProps).showAdvancedButton) {
|
||||
return cloneDeep(
|
||||
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
|
||||
)
|
||||
} else {
|
||||
return cloneDeep(schemas as FormSchema[])
|
||||
}
|
||||
})
|
||||
|
||||
const { handleToggleAdvanced } = useAdvanced({
|
||||
advanceState,
|
||||
emit,
|
||||
getProps,
|
||||
getSchema,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
})
|
||||
|
||||
const { handleFormValues, initDefault } = useFormValues({
|
||||
getProps,
|
||||
defaultValueRef,
|
||||
getSchema,
|
||||
formModel,
|
||||
})
|
||||
|
||||
useAutoFocus({
|
||||
getSchema,
|
||||
getProps,
|
||||
isInitedDefault: isInitedDefaultRef,
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
})
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
setFieldsValue,
|
||||
clearValidate,
|
||||
validate,
|
||||
validateFields,
|
||||
getFieldsValue,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
appendSchemaByField,
|
||||
removeSchemaByField,
|
||||
resetFields,
|
||||
scrollToField,
|
||||
} = useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
defaultValueRef,
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
schemaRef: schemaRef as Ref<FormSchema[]>,
|
||||
handleFormValues,
|
||||
})
|
||||
|
||||
createFormContext({
|
||||
resetAction: resetFields,
|
||||
submitAction: handleSubmit,
|
||||
})
|
||||
|
||||
watch(
|
||||
() => unref(getProps).model,
|
||||
() => {
|
||||
const { model } = unref(getProps)
|
||||
if (!model) return
|
||||
setFieldsValue(model)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => unref(getProps).schemas,
|
||||
(schemas) => {
|
||||
resetSchema(schemas ?? [])
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => getSchema.value,
|
||||
(schema) => {
|
||||
nextTick(() => {
|
||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||
modalFn?.redoModalHeight?.()
|
||||
})
|
||||
if (unref(isInitedDefaultRef)) {
|
||||
return
|
||||
}
|
||||
if (schema?.length) {
|
||||
initDefault()
|
||||
isInitedDefaultRef.value = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => formModel,
|
||||
useDebounceFn(() => {
|
||||
unref(getProps).submitOnChange && handleSubmit()
|
||||
}, 300),
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps)
|
||||
}
|
||||
|
||||
function setFormModel(key: string, value: any) {
|
||||
formModel[key] = value
|
||||
const { validateTrigger } = unref(getBindValue)
|
||||
if (!validateTrigger || validateTrigger === 'change') {
|
||||
validateFields([key]).catch((_) => {})
|
||||
}
|
||||
emit('field-value-change', key, value)
|
||||
}
|
||||
|
||||
function handleEnterPress(e: KeyboardEvent) {
|
||||
const { autoSubmitOnEnter } = unref(getProps)
|
||||
if (!autoSubmitOnEnter) return
|
||||
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
|
||||
const target: HTMLElement = e.target as HTMLElement
|
||||
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
|
||||
handleSubmit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formActionType: Partial<FormActionType> = {
|
||||
getFieldsValue,
|
||||
setFieldsValue,
|
||||
resetFields,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
setProps,
|
||||
removeSchemaByField,
|
||||
appendSchemaByField,
|
||||
clearValidate,
|
||||
validateFields,
|
||||
validate,
|
||||
submit: handleSubmit,
|
||||
scrollToField: scrollToField,
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initDefault()
|
||||
emit('register', formActionType)
|
||||
})
|
||||
|
||||
return {
|
||||
getBindValue,
|
||||
handleToggleAdvanced,
|
||||
handleEnterPress,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
advanceState,
|
||||
getRow,
|
||||
getProps,
|
||||
formElRef,
|
||||
getSchema,
|
||||
formActionType: formActionType as any,
|
||||
setFormModel,
|
||||
getFormClass,
|
||||
getFormActionBindProps: computed(
|
||||
(): Recordable => ({ ...getProps.value, ...advanceState }),
|
||||
),
|
||||
...formActionType,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-form';
|
||||
|
||||
.@{prefix-cls} {
|
||||
.ant-form-item {
|
||||
&-label label::after {
|
||||
margin: 0 6px 0 2px;
|
||||
}
|
||||
|
||||
&-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:not(.ant-form-item-with-help) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&.suffix-item {
|
||||
.ant-form-item-children {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-form-item-control {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
display: inline-flex;
|
||||
padding-left: 6px;
|
||||
margin-top: 1px;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-explain {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
.ant-form-item {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import type { Component } from 'vue'
|
||||
import type { ComponentType } from './types/index'
|
||||
|
||||
/**
|
||||
* Component list, register here to setting it in the form
|
||||
*/
|
||||
import {
|
||||
Input,
|
||||
Select,
|
||||
Radio,
|
||||
Checkbox,
|
||||
AutoComplete,
|
||||
Cascader,
|
||||
DatePicker,
|
||||
InputNumber,
|
||||
Switch,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Slider,
|
||||
Rate,
|
||||
Divider,
|
||||
} from 'ant-design-vue'
|
||||
|
||||
import ApiRadioGroup from './components/ApiRadioGroup.vue'
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue'
|
||||
import ApiSelect from './components/ApiSelect.vue'
|
||||
import ApiTree from './components/ApiTree.vue'
|
||||
import ApiTreeSelect from './components/ApiTreeSelect.vue'
|
||||
import ApiCascader from './components/ApiCascader.vue'
|
||||
import ApiTransfer from './components/ApiTransfer.vue'
|
||||
import { BasicUpload } from '/@/components/Upload'
|
||||
import { StrengthMeter } from '/@/components/StrengthMeter'
|
||||
import { IconPicker } from '/@/components/Icon'
|
||||
import { CountdownInput } from '/@/components/CountDown'
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>()
|
||||
|
||||
componentMap.set('Input', Input)
|
||||
componentMap.set('InputGroup', Input.Group)
|
||||
componentMap.set('InputPassword', Input.Password)
|
||||
componentMap.set('InputSearch', Input.Search)
|
||||
componentMap.set('InputTextArea', Input.TextArea)
|
||||
componentMap.set('InputNumber', InputNumber)
|
||||
componentMap.set('AutoComplete', AutoComplete)
|
||||
|
||||
componentMap.set('Select', Select)
|
||||
componentMap.set('ApiSelect', ApiSelect)
|
||||
componentMap.set('ApiTree', ApiTree)
|
||||
componentMap.set('TreeSelect', TreeSelect)
|
||||
componentMap.set('ApiTreeSelect', ApiTreeSelect)
|
||||
componentMap.set('ApiRadioGroup', ApiRadioGroup)
|
||||
componentMap.set('Switch', Switch)
|
||||
componentMap.set('RadioButtonGroup', RadioButtonGroup)
|
||||
componentMap.set('RadioGroup', Radio.Group)
|
||||
componentMap.set('Checkbox', Checkbox)
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group)
|
||||
componentMap.set('ApiCascader', ApiCascader)
|
||||
componentMap.set('Cascader', Cascader)
|
||||
componentMap.set('Slider', Slider)
|
||||
componentMap.set('Rate', Rate)
|
||||
componentMap.set('ApiTransfer', ApiTransfer)
|
||||
|
||||
componentMap.set('DatePicker', DatePicker)
|
||||
componentMap.set('MonthPicker', DatePicker.MonthPicker)
|
||||
componentMap.set('RangePicker', DatePicker.RangePicker)
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker)
|
||||
componentMap.set('TimePicker', TimePicker)
|
||||
componentMap.set('StrengthMeter', StrengthMeter)
|
||||
componentMap.set('IconPicker', IconPicker)
|
||||
componentMap.set('InputCountDown', CountdownInput)
|
||||
|
||||
componentMap.set('Upload', BasicUpload)
|
||||
componentMap.set('Divider', Divider)
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component)
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName)
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<template>
|
||||
<a-cascader
|
||||
v-model:value="state"
|
||||
:options="options"
|
||||
:load-data="loadData"
|
||||
change-on-select
|
||||
@change="handleChange"
|
||||
:displayRender="handleRenderDisplay"
|
||||
>
|
||||
<template #suffixIcon v-if="loading">
|
||||
<LoadingOutlined spin />
|
||||
</template>
|
||||
<template #notFoundContent v-if="loading">
|
||||
<span>
|
||||
<LoadingOutlined spin class="mr-1" />
|
||||
{{ t('component.form.apiSelectNotFound') }}
|
||||
</span>
|
||||
</template>
|
||||
</a-cascader>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue'
|
||||
import { Cascader } from 'ant-design-vue'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
loading?: boolean
|
||||
isLeaf?: boolean
|
||||
children?: Option[]
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'ApiCascader',
|
||||
components: {
|
||||
LoadingOutlined,
|
||||
[Cascader.name]: Cascader,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
},
|
||||
api: {
|
||||
type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>,
|
||||
default: null,
|
||||
},
|
||||
numberToString: propTypes.bool,
|
||||
resultField: propTypes.string.def(''),
|
||||
labelField: propTypes.string.def('label'),
|
||||
valueField: propTypes.string.def('value'),
|
||||
childrenField: propTypes.string.def('children'),
|
||||
asyncFetchParamKey: propTypes.string.def('parentCode'),
|
||||
immediate: propTypes.bool.def(true),
|
||||
// init fetch params
|
||||
initFetchParams: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 是否有下级,默认是
|
||||
isLeaf: {
|
||||
type: Function as PropType<(arg: Recordable) => boolean>,
|
||||
default: null,
|
||||
},
|
||||
displayRenderArray: {
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
emits: ['change', 'defaultChange'],
|
||||
setup(props, { emit }) {
|
||||
const apiData = ref<any[]>([])
|
||||
const options = ref<Option[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const emitData = ref<any[]>([])
|
||||
const isFirstLoad = ref(true)
|
||||
const { t } = useI18n()
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
|
||||
|
||||
watch(
|
||||
apiData,
|
||||
(data) => {
|
||||
const opts = generatorOptions(data)
|
||||
options.value = opts
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
function generatorOptions(options: any[]): Option[] {
|
||||
const { labelField, valueField, numberToString, childrenField, isLeaf } = props
|
||||
return options.reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
const value = next[valueField]
|
||||
const item = {
|
||||
...omit(next, [labelField, valueField]),
|
||||
label: next[labelField],
|
||||
value: numberToString ? `${value}` : value,
|
||||
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
|
||||
}
|
||||
const children = Reflect.get(next, childrenField)
|
||||
if (children) {
|
||||
Reflect.set(item, childrenField, generatorOptions(children))
|
||||
}
|
||||
prev.push(item)
|
||||
}
|
||||
return prev
|
||||
}, [] as Option[])
|
||||
}
|
||||
|
||||
async function initialFetch() {
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) return
|
||||
apiData.value = []
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api(props.initFetchParams)
|
||||
if (Array.isArray(res)) {
|
||||
apiData.value = res
|
||||
return
|
||||
}
|
||||
if (props.resultField) {
|
||||
apiData.value = get(res, props.resultField) || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(selectedOptions: Option[]) {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1]
|
||||
targetOption.loading = true
|
||||
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) return
|
||||
try {
|
||||
const res = await api({
|
||||
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
|
||||
})
|
||||
if (Array.isArray(res)) {
|
||||
const children = generatorOptions(res)
|
||||
targetOption.children = children
|
||||
return
|
||||
}
|
||||
if (props.resultField) {
|
||||
const children = generatorOptions(get(res, props.resultField) || [])
|
||||
targetOption.children = children
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
targetOption.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && initialFetch()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.initFetchParams,
|
||||
() => {
|
||||
!unref(isFirstLoad) && initialFetch()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
function handleChange(keys, args) {
|
||||
emitData.value = keys
|
||||
emit('defaultChange', keys, args)
|
||||
}
|
||||
|
||||
function handleRenderDisplay({ labels, selectedOptions }) {
|
||||
if (unref(emitData).length === selectedOptions.length) {
|
||||
return labels.join(' / ')
|
||||
}
|
||||
if (props.displayRenderArray) {
|
||||
return props.displayRenderArray.join(' / ')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
options,
|
||||
loading,
|
||||
t,
|
||||
handleChange,
|
||||
loadData,
|
||||
handleRenderDisplay,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<!--
|
||||
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
||||
-->
|
||||
<template>
|
||||
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
|
||||
<template v-for="item in getOptions" :key="`${item.value}`">
|
||||
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
|
||||
{{ item.label }}
|
||||
</RadioButton>
|
||||
<Radio v-else :value="item.value" :disabled="item.disabled">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</template>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
|
||||
import { Radio } from 'ant-design-vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { get, omit } from 'lodash-es';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ApiRadioGroup',
|
||||
components: {
|
||||
RadioGroup: Radio.Group,
|
||||
RadioButton: Radio.Button,
|
||||
Radio,
|
||||
},
|
||||
props: {
|
||||
api: {
|
||||
type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
|
||||
default: null,
|
||||
},
|
||||
params: {
|
||||
type: [Object, String] as PropType<Recordable | string>,
|
||||
default: () => ({}),
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||
},
|
||||
isBtn: {
|
||||
type: [Boolean] as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
numberToString: propTypes.bool,
|
||||
resultField: propTypes.string.def(''),
|
||||
labelField: propTypes.string.def('label'),
|
||||
valueField: propTypes.string.def('value'),
|
||||
immediate: propTypes.bool.def(true),
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const options = ref<OptionsItem[]>([]);
|
||||
const loading = ref(false);
|
||||
const isFirstLoad = ref(true);
|
||||
const emitData = ref<any[]>([]);
|
||||
const attrs = useAttrs();
|
||||
const { t } = useI18n();
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props);
|
||||
|
||||
// Processing options value
|
||||
const getOptions = computed(() => {
|
||||
const { labelField, valueField, numberToString } = props;
|
||||
|
||||
return unref(options).reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
const value = next[valueField];
|
||||
prev.push({
|
||||
label: next[labelField],
|
||||
value: numberToString ? `${value}` : value,
|
||||
...omit(next, [labelField, valueField]),
|
||||
});
|
||||
}
|
||||
return prev;
|
||||
}, [] as OptionsItem[]);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && fetch();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
!unref(isFirstLoad) && fetch();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
async function fetch() {
|
||||
const api = props.api;
|
||||
if (!api || !isFunction(api)) return;
|
||||
options.value = [];
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await api(props.params);
|
||||
if (Array.isArray(res)) {
|
||||
options.value = res;
|
||||
emitChange();
|
||||
return;
|
||||
}
|
||||
if (props.resultField) {
|
||||
options.value = get(res, props.resultField) || [];
|
||||
}
|
||||
emitChange();
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function emitChange() {
|
||||
emit('options-change', unref(getOptions));
|
||||
}
|
||||
|
||||
function handleChange(_, ...args) {
|
||||
emitData.value = args;
|
||||
}
|
||||
|
||||
return { state, getOptions, attrs, loading, t, handleChange, props };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<Select
|
||||
@dropdown-visible-change="handleFetch"
|
||||
v-bind="$attrs"
|
||||
@change="handleChange"
|
||||
:options="getOptions"
|
||||
v-model:value="state"
|
||||
>
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template #suffixIcon v-if="loading">
|
||||
<LoadingOutlined spin />
|
||||
</template>
|
||||
<template #notFoundContent v-if="loading">
|
||||
<span>
|
||||
<LoadingOutlined spin class="mr-1" />
|
||||
{{ t('component.form.apiSelectNotFound') }}
|
||||
</span>
|
||||
</template>
|
||||
</Select>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue'
|
||||
import { Select } from 'ant-design-vue'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem'
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
|
||||
type OptionsItem = { label: string; value: string; disabled?: boolean }
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ApiSelect',
|
||||
components: {
|
||||
Select,
|
||||
LoadingOutlined,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: [Array, Object, String, Number],
|
||||
numberToString: propTypes.bool,
|
||||
api: {
|
||||
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
|
||||
default: null,
|
||||
},
|
||||
// api params
|
||||
params: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
// support xxx.xxx.xx
|
||||
resultField: propTypes.string.def(''),
|
||||
labelField: propTypes.string.def('label'),
|
||||
valueField: propTypes.string.def('value'),
|
||||
immediate: propTypes.bool.def(true),
|
||||
alwaysLoad: propTypes.bool.def(false),
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const options = ref<OptionsItem[]>([])
|
||||
const loading = ref(false)
|
||||
const isFirstLoad = ref(true)
|
||||
const emitData = ref<any[]>([])
|
||||
const attrs = useAttrs()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
|
||||
|
||||
const getOptions = computed(() => {
|
||||
const { labelField, valueField, numberToString } = props
|
||||
|
||||
return unref(options).reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
const value = next[valueField]
|
||||
prev.push({
|
||||
...omit(next, [labelField, valueField]),
|
||||
label: next[labelField],
|
||||
value: numberToString ? `${value}` : value,
|
||||
})
|
||||
}
|
||||
return prev
|
||||
}, [] as OptionsItem[])
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && !props.alwaysLoad && fetch()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
!unref(isFirstLoad) && fetch()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
async function fetch() {
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) return
|
||||
options.value = []
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await api(props.params)
|
||||
if (Array.isArray(res)) {
|
||||
options.value = res
|
||||
emitChange()
|
||||
return
|
||||
}
|
||||
if (props.resultField) {
|
||||
options.value = get(res, props.resultField) || []
|
||||
}
|
||||
emitChange()
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetch(visible) {
|
||||
if (visible) {
|
||||
if (props.alwaysLoad) {
|
||||
await fetch()
|
||||
} else if (!props.immediate && unref(isFirstLoad)) {
|
||||
await fetch()
|
||||
isFirstLoad.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitChange() {
|
||||
emit('options-change', unref(getOptions))
|
||||
}
|
||||
|
||||
function handleChange(_, ...args) {
|
||||
emitData.value = args
|
||||
}
|
||||
|
||||
return { state, attrs, getOptions, loading, t, handleFetch, handleChange }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<Transfer
|
||||
:data-source="getdataSource"
|
||||
:filter-option="filterOption"
|
||||
:render="(item) => item.title"
|
||||
:showSelectAll="showSelectAll"
|
||||
:selectedKeys="selectedKeys"
|
||||
:targetKeys="getTargetKeys"
|
||||
:showSearch="showSearch"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue'
|
||||
import { Transfer } from 'ant-design-vue'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
|
||||
export default defineComponent({
|
||||
name: 'ApiTransfer',
|
||||
components: { Transfer },
|
||||
props: {
|
||||
value: { type: Array as PropType<Array<string>> },
|
||||
api: {
|
||||
type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>,
|
||||
default: null,
|
||||
},
|
||||
params: { type: Object },
|
||||
dataSource: { type: Array as PropType<Array<TransferItem>> },
|
||||
immediate: propTypes.bool.def(true),
|
||||
alwaysLoad: propTypes.bool.def(false),
|
||||
afterFetch: { type: Function as PropType<Fn> },
|
||||
resultField: propTypes.string.def(''),
|
||||
labelField: propTypes.string.def('title'),
|
||||
valueField: propTypes.string.def('key'),
|
||||
showSearch: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
filterOption: {
|
||||
type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>,
|
||||
},
|
||||
selectedKeys: { type: Array as PropType<Array<string>> },
|
||||
showSelectAll: { type: Boolean, default: false },
|
||||
targetKeys: { type: Array as PropType<Array<string>> },
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const _dataSource = ref<TransferItem[]>([])
|
||||
const _targetKeys = ref<string[]>([])
|
||||
const { t } = useI18n()
|
||||
|
||||
const getAttrs = computed(() => {
|
||||
return {
|
||||
...(!props.api ? { dataSource: unref(_dataSource) } : {}),
|
||||
...attrs,
|
||||
}
|
||||
})
|
||||
const getdataSource = computed(() => {
|
||||
const { labelField, valueField } = props
|
||||
|
||||
return unref(_dataSource).reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
prev.push({
|
||||
...omit(next, [labelField, valueField]),
|
||||
title: next[labelField],
|
||||
key: next[valueField],
|
||||
})
|
||||
}
|
||||
return prev
|
||||
}, [] as TransferItem[])
|
||||
})
|
||||
const getTargetKeys = computed<string[]>(() => {
|
||||
if (unref(_targetKeys).length > 0) {
|
||||
return unref(_targetKeys)
|
||||
}
|
||||
if (Array.isArray(props.value)) {
|
||||
return props.value
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
|
||||
_targetKeys.value = keys
|
||||
console.log(direction)
|
||||
console.log(moveKeys)
|
||||
emit('change', keys)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
props.immediate && !props.alwaysLoad && fetch()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
fetch()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
async function fetch() {
|
||||
const api = props.api
|
||||
if (!api || !isFunction(api)) {
|
||||
if (Array.isArray(props.dataSource)) {
|
||||
_dataSource.value = props.dataSource
|
||||
}
|
||||
return
|
||||
}
|
||||
_dataSource.value = []
|
||||
try {
|
||||
const res = await api(props.params)
|
||||
if (Array.isArray(res)) {
|
||||
_dataSource.value = res
|
||||
emitChange()
|
||||
return
|
||||
}
|
||||
if (props.resultField) {
|
||||
_dataSource.value = get(res, props.resultField) || []
|
||||
}
|
||||
emitChange()
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
function emitChange() {
|
||||
emit('options-change', unref(getdataSource))
|
||||
}
|
||||
return { getTargetKeys, getdataSource, t, getAttrs, handleChange }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<a-tree v-bind="getAttrs" @change="handleChange">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template #suffixIcon v-if="loading">
|
||||
<LoadingOutlined spin />
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'
|
||||
import { Tree } from 'ant-design-vue'
|
||||
import { isArray, isFunction } from '/@/utils/is'
|
||||
import { get } from 'lodash-es'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
export default defineComponent({
|
||||
name: 'ApiTree',
|
||||
components: { ATree: Tree, LoadingOutlined },
|
||||
props: {
|
||||
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
|
||||
params: { type: Object },
|
||||
immediate: { type: Boolean, default: true },
|
||||
resultField: propTypes.string.def(''),
|
||||
afterFetch: { type: Function as PropType<Fn> },
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const treeData = ref<Recordable[]>([])
|
||||
const isFirstLoaded = ref<Boolean>(false)
|
||||
const loading = ref(false)
|
||||
const getAttrs = computed(() => {
|
||||
return {
|
||||
...(props.api ? { treeData: unref(treeData) } : {}),
|
||||
...attrs,
|
||||
}
|
||||
})
|
||||
|
||||
function handleChange(...args) {
|
||||
emit('change', ...args)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
!unref(isFirstLoaded) && fetch()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.immediate,
|
||||
(v) => {
|
||||
v && !isFirstLoaded.value && fetch()
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
props.immediate && fetch()
|
||||
})
|
||||
|
||||
async function fetch() {
|
||||
const { api, afterFetch } = props
|
||||
if (!api || !isFunction(api)) return
|
||||
loading.value = true
|
||||
treeData.value = []
|
||||
let result
|
||||
try {
|
||||
result = await api(props.params)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
if (afterFetch && isFunction(afterFetch)) {
|
||||
result = afterFetch(result)
|
||||
}
|
||||
loading.value = false
|
||||
if (!result) return
|
||||
if (!isArray(result)) {
|
||||
result = get(result, props.resultField)
|
||||
}
|
||||
treeData.value = (result as Recordable[]) || []
|
||||
isFirstLoaded.value = true
|
||||
emit('options-change', treeData.value)
|
||||
}
|
||||
return { getAttrs, loading, handleChange }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<a-tree-select v-bind="getAttrs" @change="handleChange">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template #suffixIcon v-if="loading">
|
||||
<LoadingOutlined spin />
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
|
||||
import { TreeSelect } from 'ant-design-vue';
|
||||
import { isArray, isFunction } from '/@/utils/is';
|
||||
import { get } from 'lodash-es';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
export default defineComponent({
|
||||
name: 'ApiTreeSelect',
|
||||
components: { ATreeSelect: TreeSelect, LoadingOutlined },
|
||||
props: {
|
||||
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
|
||||
params: { type: Object },
|
||||
immediate: { type: Boolean, default: true },
|
||||
resultField: propTypes.string.def(''),
|
||||
},
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const treeData = ref<Recordable[]>([]);
|
||||
const isFirstLoaded = ref<Boolean>(false);
|
||||
const loading = ref(false);
|
||||
const getAttrs = computed(() => {
|
||||
return {
|
||||
...(props.api ? { treeData: unref(treeData) } : {}),
|
||||
...attrs,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(...args) {
|
||||
emit('change', ...args);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => {
|
||||
!unref(isFirstLoaded) && fetch();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.immediate,
|
||||
(v) => {
|
||||
v && !isFirstLoaded.value && fetch();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
props.immediate && fetch();
|
||||
});
|
||||
|
||||
async function fetch() {
|
||||
const { api } = props;
|
||||
if (!api || !isFunction(api)) return;
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result;
|
||||
try {
|
||||
result = await api(props.params);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
loading.value = false;
|
||||
if (!result) return;
|
||||
if (!isArray(result)) {
|
||||
result = get(result, props.resultField);
|
||||
}
|
||||
treeData.value = (result as Recordable[]) || [];
|
||||
isFirstLoaded.value = true;
|
||||
emit('options-change', treeData.value);
|
||||
}
|
||||
return { getAttrs, loading, handleChange };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
|
||||
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
|
||||
<FormItem>
|
||||
<slot name="resetBefore"></slot>
|
||||
<Button
|
||||
type="default"
|
||||
class="mr-2"
|
||||
v-bind="getResetBtnOptions"
|
||||
@click="resetAction"
|
||||
v-if="showResetButton"
|
||||
>
|
||||
{{ getResetBtnOptions.text }}
|
||||
</Button>
|
||||
<slot name="submitBefore"></slot>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
class="mr-2"
|
||||
v-bind="getSubmitBtnOptions"
|
||||
@click="submitAction"
|
||||
v-if="showSubmitButton"
|
||||
>
|
||||
{{ getSubmitBtnOptions.text }}
|
||||
</Button>
|
||||
|
||||
<slot name="advanceBefore"></slot>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="toggleAdvanced"
|
||||
v-if="showAdvancedButton && !hideAdvanceBtn"
|
||||
>
|
||||
{{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
|
||||
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
|
||||
</Button>
|
||||
<slot name="advanceAfter"></slot>
|
||||
</FormItem>
|
||||
</div>
|
||||
</a-col>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { ColEx } from '../types/index';
|
||||
//import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import { Button, ButtonProps } from '/@/components/Button';
|
||||
import { BasicArrow } from '/@/components/Basic';
|
||||
import { useFormContext } from '../hooks/useFormContext';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
type ButtonOptions = Partial<ButtonProps> & { text: string };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormAction',
|
||||
components: {
|
||||
FormItem: Form.Item,
|
||||
Button,
|
||||
BasicArrow,
|
||||
[Col.name]: Col,
|
||||
},
|
||||
props: {
|
||||
showActionButtonGroup: propTypes.bool.def(true),
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
showAdvancedButton: propTypes.bool.def(true),
|
||||
resetButtonOptions: {
|
||||
type: Object as PropType<ButtonOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
submitButtonOptions: {
|
||||
type: Object as PropType<ButtonOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
actionColOptions: {
|
||||
type: Object as PropType<Partial<ColEx>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
actionSpan: propTypes.number.def(6),
|
||||
isAdvanced: propTypes.bool,
|
||||
hideAdvanceBtn: propTypes.bool,
|
||||
},
|
||||
emits: ['toggle-advanced'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const actionColOpt = computed(() => {
|
||||
const { showAdvancedButton, actionSpan: span, actionColOptions } = props;
|
||||
const actionSpan = 24 - span;
|
||||
const advancedSpanObj = showAdvancedButton
|
||||
? { span: actionSpan < 6 ? 24 : actionSpan }
|
||||
: {};
|
||||
const actionColOpt: Partial<ColEx> = {
|
||||
style: { textAlign: 'right' },
|
||||
span: showAdvancedButton ? 6 : 4,
|
||||
...advancedSpanObj,
|
||||
...actionColOptions,
|
||||
};
|
||||
return actionColOpt;
|
||||
});
|
||||
|
||||
const getResetBtnOptions = computed((): ButtonOptions => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('common.resetText'),
|
||||
},
|
||||
props.resetButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
const getSubmitBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('common.queryText'),
|
||||
},
|
||||
props.submitButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
function toggleAdvanced() {
|
||||
emit('toggle-advanced');
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
actionColOpt,
|
||||
getResetBtnOptions,
|
||||
getSubmitBtnOptions,
|
||||
toggleAdvanced,
|
||||
...useFormContext(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
<script lang="tsx">
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import { computed, defineComponent, toRefs, unref } from 'vue'
|
||||
import type { FormActionType, FormProps, FormSchema } from '../types/form'
|
||||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
|
||||
import type { TableActionType } from '/@/components/Table'
|
||||
import { Col, Divider, Form } from 'ant-design-vue'
|
||||
import { componentMap } from '../componentMap'
|
||||
import { BasicHelp } from '/@/components/Basic'
|
||||
import { isBoolean, isFunction, isNull } from '/@/utils/is'
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper'
|
||||
import { createPlaceholderMessage, setComponentRuleType } from '../helper'
|
||||
import { cloneDeep, upperFirst } from 'lodash-es'
|
||||
import { useItemLabelWidth } from '../hooks/useLabelWidth'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormItem',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
schema: {
|
||||
type: Object as PropType<FormSchema>,
|
||||
default: () => ({}),
|
||||
},
|
||||
formProps: {
|
||||
type: Object as PropType<FormProps>,
|
||||
default: () => ({}),
|
||||
},
|
||||
allDefaultValues: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
setFormModel: {
|
||||
type: Function as PropType<(key: string, value: any) => void>,
|
||||
default: null,
|
||||
},
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
formActionType: {
|
||||
type: Object as PropType<FormActionType>,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
const { schema, formProps } = toRefs(props) as {
|
||||
schema: Ref<FormSchema>
|
||||
formProps: Ref<FormProps>
|
||||
}
|
||||
|
||||
const itemLabelWidthProp = useItemLabelWidth(schema, formProps)
|
||||
|
||||
const getValues = computed(() => {
|
||||
const { allDefaultValues, formModel, schema } = props
|
||||
const { mergeDynamicData } = props.formProps
|
||||
return {
|
||||
field: schema.field,
|
||||
model: formModel,
|
||||
values: {
|
||||
...mergeDynamicData,
|
||||
...allDefaultValues,
|
||||
...formModel,
|
||||
} as Recordable,
|
||||
schema: schema,
|
||||
}
|
||||
})
|
||||
|
||||
const getComponentsProps = computed(() => {
|
||||
const { schema, tableAction, formModel, formActionType } = props
|
||||
let { componentProps = {} } = schema
|
||||
if (isFunction(componentProps)) {
|
||||
componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {}
|
||||
}
|
||||
if (schema.component === 'Divider') {
|
||||
componentProps = Object.assign({ type: 'horizontal' }, componentProps, {
|
||||
orientation: 'left',
|
||||
plain: true,
|
||||
})
|
||||
}
|
||||
return componentProps as Recordable
|
||||
})
|
||||
|
||||
const getDisable = computed(() => {
|
||||
const { disabled: globDisabled } = props.formProps
|
||||
const { dynamicDisabled } = props.schema
|
||||
const { disabled: itemDisabled = false } = unref(getComponentsProps)
|
||||
let disabled = !!globDisabled || itemDisabled
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled
|
||||
}
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled(unref(getValues))
|
||||
}
|
||||
return disabled
|
||||
})
|
||||
|
||||
function getShow(): { isShow: boolean; isIfShow: boolean } {
|
||||
const { show, ifShow } = props.schema
|
||||
const { showAdvancedButton } = props.formProps
|
||||
const itemIsAdvanced = showAdvancedButton
|
||||
? isBoolean(props.schema.isAdvanced)
|
||||
? props.schema.isAdvanced
|
||||
: true
|
||||
: true
|
||||
|
||||
let isShow = true
|
||||
let isIfShow = true
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show
|
||||
}
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show(unref(getValues))
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(unref(getValues))
|
||||
}
|
||||
isShow = isShow && itemIsAdvanced
|
||||
return { isShow, isIfShow }
|
||||
}
|
||||
|
||||
function handleRules(): ValidationRule[] {
|
||||
const {
|
||||
rules: defRules = [],
|
||||
component,
|
||||
rulesMessageJoinLabel,
|
||||
label,
|
||||
dynamicRules,
|
||||
required,
|
||||
} = props.schema
|
||||
|
||||
if (isFunction(dynamicRules)) {
|
||||
return dynamicRules(unref(getValues)) as ValidationRule[]
|
||||
}
|
||||
|
||||
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]
|
||||
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps
|
||||
|
||||
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
|
||||
? rulesMessageJoinLabel
|
||||
: globalRulesMessageJoinLabel
|
||||
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`
|
||||
|
||||
function validator(rule: any, value: any) {
|
||||
const msg = rule.message || defaultMsg
|
||||
if (value === undefined || isNull(value)) {
|
||||
// 空值
|
||||
return Promise.reject(msg)
|
||||
} else if (Array.isArray(value) && value.length === 0) {
|
||||
// 数组类型
|
||||
return Promise.reject(msg)
|
||||
} else if (typeof value === 'string' && value.trim() === '') {
|
||||
// 空字符串
|
||||
return Promise.reject(msg)
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
Reflect.has(value, 'checked') &&
|
||||
Reflect.has(value, 'halfChecked') &&
|
||||
Array.isArray(value.checked) &&
|
||||
Array.isArray(value.halfChecked) &&
|
||||
value.checked.length === 0 &&
|
||||
value.halfChecked.length === 0
|
||||
) {
|
||||
// 非关联选择的tree组件
|
||||
return Promise.reject(msg)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const getRequired = isFunction(required) ? required(unref(getValues)) : required
|
||||
|
||||
/*
|
||||
* 1、若设置了required属性,又没有其他的rules,就创建一个验证规则;
|
||||
* 2、若设置了required属性,又存在其他的rules,则只rules中不存在required属性时,才添加验证required的规则
|
||||
* 也就是说rules中的required,优先级大于required
|
||||
*/
|
||||
if (getRequired) {
|
||||
if (!rules || rules.length === 0) {
|
||||
rules = [{ required: getRequired, validator }]
|
||||
} else {
|
||||
const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'))
|
||||
|
||||
if (requiredIndex === -1) {
|
||||
rules.push({ required: getRequired, validator })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const requiredRuleIndex: number = rules.findIndex(
|
||||
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
|
||||
)
|
||||
|
||||
if (requiredRuleIndex !== -1) {
|
||||
const rule = rules[requiredRuleIndex]
|
||||
const { isShow } = getShow()
|
||||
if (!isShow) {
|
||||
rule.required = false
|
||||
}
|
||||
if (component) {
|
||||
if (!Reflect.has(rule, 'type')) {
|
||||
rule.type = component === 'InputNumber' ? 'number' : 'string'
|
||||
}
|
||||
|
||||
rule.message = rule.message || defaultMsg
|
||||
|
||||
if (component.includes('Input') || component.includes('Textarea')) {
|
||||
rule.whitespace = true
|
||||
}
|
||||
const valueFormat = unref(getComponentsProps)?.valueFormat
|
||||
setComponentRuleType(rule, component, valueFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum input length rule check
|
||||
const characterInx = rules.findIndex((val) => val.max)
|
||||
if (characterInx !== -1 && !rules[characterInx].validator) {
|
||||
rules[characterInx].message =
|
||||
rules[characterInx].message ||
|
||||
t('component.form.maxTip', [rules[characterInx].max] as Recordable)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
function renderComponent() {
|
||||
const {
|
||||
renderComponentContent,
|
||||
component,
|
||||
field,
|
||||
changeEvent = 'change',
|
||||
valueField,
|
||||
} = props.schema
|
||||
|
||||
const isCheck = component && ['Switch', 'Checkbox'].includes(component)
|
||||
|
||||
const eventKey = `on${upperFirst(changeEvent)}`
|
||||
|
||||
const on = {
|
||||
[eventKey]: (...args: Nullable<Recordable>[]) => {
|
||||
const [e] = args
|
||||
if (propsData[eventKey]) {
|
||||
propsData[eventKey](...args)
|
||||
}
|
||||
const target = e ? e.target : null
|
||||
const value = target ? (isCheck ? target.checked : target.value) : e
|
||||
props.setFormModel(field, value)
|
||||
},
|
||||
}
|
||||
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>
|
||||
|
||||
const { autoSetPlaceHolder, size } = props.formProps
|
||||
const propsData: Recordable = {
|
||||
allowClear: true,
|
||||
getPopupContainer: (trigger: Element) => trigger.parentNode,
|
||||
size,
|
||||
...unref(getComponentsProps),
|
||||
disabled: unref(getDisable),
|
||||
}
|
||||
|
||||
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder
|
||||
// RangePicker place is an array
|
||||
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
|
||||
propsData.placeholder =
|
||||
unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
|
||||
}
|
||||
propsData.codeField = field
|
||||
propsData.formValues = unref(getValues)
|
||||
|
||||
const bindValue: Recordable = {
|
||||
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
|
||||
}
|
||||
|
||||
const compAttr: Recordable = {
|
||||
...propsData,
|
||||
...on,
|
||||
...bindValue,
|
||||
}
|
||||
|
||||
if (!renderComponentContent) {
|
||||
return <Comp {...compAttr} />
|
||||
}
|
||||
const compSlot = isFunction(renderComponentContent)
|
||||
? { ...renderComponentContent(unref(getValues)) }
|
||||
: {
|
||||
default: () => renderComponentContent,
|
||||
}
|
||||
return <Comp {...compAttr}>{compSlot}</Comp>
|
||||
}
|
||||
|
||||
function renderLabelHelpMessage() {
|
||||
const { label, helpMessage, helpComponentProps, subLabel } = props.schema
|
||||
const renderLabel = subLabel ? (
|
||||
<span>
|
||||
{label} <span class="text-secondary">{subLabel}</span>
|
||||
</span>
|
||||
) : (
|
||||
label
|
||||
)
|
||||
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage
|
||||
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
|
||||
return renderLabel
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
{renderLabel}
|
||||
<BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field, suffix, component } = props.schema
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
|
||||
const { colon } = props.formProps
|
||||
|
||||
if (component === 'Divider') {
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
|
||||
</Col>
|
||||
)
|
||||
} else {
|
||||
const getContent = () => {
|
||||
return slot
|
||||
? getSlot(slots, slot, unref(getValues))
|
||||
: render
|
||||
? render(unref(getValues))
|
||||
: renderComponent()
|
||||
}
|
||||
|
||||
const showSuffix = !!suffix
|
||||
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
name={field}
|
||||
colon={colon}
|
||||
class={{ 'suffix-item': showSuffix }}
|
||||
{...(itemProps as Recordable)}
|
||||
label={renderLabelHelpMessage()}
|
||||
rules={handleRules()}
|
||||
labelCol={labelCol}
|
||||
wrapperCol={wrapperCol}
|
||||
>
|
||||
<div style="display:flex">
|
||||
<div style="flex:1;">{getContent()}</div>
|
||||
{showSuffix && <span class="suffix">{getSuffix}</span>}
|
||||
</div>
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { colProps = {}, colSlot, renderColContent, component } = props.schema
|
||||
if (!componentMap.has(component)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { baseColProps = {} } = props.formProps
|
||||
const realColProps = { ...baseColProps, ...colProps }
|
||||
const { isIfShow, isShow } = getShow()
|
||||
const values = unref(getValues)
|
||||
|
||||
const getContent = () => {
|
||||
return colSlot
|
||||
? getSlot(slots, colSlot, values)
|
||||
: renderColContent
|
||||
? renderColContent(values)
|
||||
: renderItem()
|
||||
}
|
||||
|
||||
return (
|
||||
isIfShow && (
|
||||
<Col {...realColProps} v-show={isShow}>
|
||||
{getContent()}
|
||||
</Col>
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<!--
|
||||
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
||||
-->
|
||||
<template>
|
||||
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
|
||||
<template v-for="item in getOptions" :key="`${item.value}`">
|
||||
<RadioButton :value="item.value" :disabled="item.disabled">
|
||||
{{ item.label }}
|
||||
</RadioButton>
|
||||
</template>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { Radio } from 'ant-design-vue';
|
||||
import { isString } from '/@/utils/is';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
|
||||
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
|
||||
type RadioItem = string | OptionsItem;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RadioButtonGroup',
|
||||
components: {
|
||||
RadioGroup: Radio.Group,
|
||||
RadioButton: Radio.Button,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<RadioItem[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const attrs = useAttrs();
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props);
|
||||
|
||||
// Processing options value
|
||||
const getOptions = computed((): OptionsItem[] => {
|
||||
const { options } = props;
|
||||
if (!options || options?.length === 0) return [];
|
||||
|
||||
const isStringArr = options.some((item) => isString(item));
|
||||
if (!isStringArr) return options as OptionsItem[];
|
||||
|
||||
return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
|
||||
});
|
||||
|
||||
return { state, getOptions, attrs };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
|
||||
import type { ComponentType } from './types/index'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { isNumber, isObject } from '/@/utils/is'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component.includes('Input') || component.includes('Complete')) {
|
||||
return t('common.inputText')
|
||||
}
|
||||
if (component.includes('Picker')) {
|
||||
return t('common.chooseText')
|
||||
}
|
||||
if (
|
||||
component.includes('Select') ||
|
||||
component.includes('Cascader') ||
|
||||
component.includes('Checkbox') ||
|
||||
component.includes('Radio') ||
|
||||
component.includes('Switch')
|
||||
) {
|
||||
// return `请选择${label}`;
|
||||
return t('common.chooseText')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']
|
||||
|
||||
function genType() {
|
||||
return [...DATE_TYPE, 'RangePicker']
|
||||
}
|
||||
|
||||
export function setComponentRuleType(
|
||||
rule: ValidationRule,
|
||||
component: ComponentType,
|
||||
valueFormat: string,
|
||||
) {
|
||||
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
|
||||
rule.type = valueFormat ? 'string' : 'object'
|
||||
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {
|
||||
rule.type = 'array'
|
||||
} else if (['InputNumber'].includes(component)) {
|
||||
rule.type = 'number'
|
||||
}
|
||||
}
|
||||
|
||||
export function processDateValue(attr: Recordable, component: string) {
|
||||
const { valueFormat, value } = attr
|
||||
if (valueFormat) {
|
||||
attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value
|
||||
} else if (DATE_TYPE.includes(component) && value) {
|
||||
attr.value = dateUtil(attr.value)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleInputNumberValue(component?: ComponentType, val?: any) {
|
||||
if (!component) return val
|
||||
if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
|
||||
return val && isNumber(val) ? `${val}` : val
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
export const dateItemType = genType()
|
||||
|
||||
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea']
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
import type { ColEx } from '../types'
|
||||
import type { AdvanceState } from '../types/hooks'
|
||||
import { ComputedRef, getCurrentInstance, Ref } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { computed, unref, watch } from 'vue'
|
||||
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
|
||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
|
||||
const BASIC_COL_LEN = 24
|
||||
|
||||
interface UseAdvancedContext {
|
||||
advanceState: AdvanceState
|
||||
emit: EmitType
|
||||
getProps: ComputedRef<FormProps>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
formModel: Recordable
|
||||
defaultValueRef: Ref<Recordable>
|
||||
}
|
||||
|
||||
export default function ({
|
||||
advanceState,
|
||||
emit,
|
||||
getProps,
|
||||
getSchema,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
}: UseAdvancedContext) {
|
||||
const vm = getCurrentInstance()
|
||||
|
||||
const { realWidthRef, screenEnum, screenRef } = useBreakpoint()
|
||||
|
||||
const getEmptySpan = computed((): number => {
|
||||
if (!advanceState.isAdvanced) {
|
||||
return 0
|
||||
}
|
||||
// For some special cases, you need to manually specify additional blank lines
|
||||
const emptySpan = unref(getProps).emptySpan || 0
|
||||
|
||||
if (isNumber(emptySpan)) {
|
||||
return emptySpan
|
||||
}
|
||||
if (isObject(emptySpan)) {
|
||||
const { span = 0 } = emptySpan
|
||||
const screen = unref(screenRef) as string
|
||||
|
||||
const screenSpan = (emptySpan as any)[screen.toLowerCase()]
|
||||
return screenSpan || span || 0
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30)
|
||||
|
||||
watch(
|
||||
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
|
||||
() => {
|
||||
const { showAdvancedButton } = unref(getProps)
|
||||
if (showAdvancedButton) {
|
||||
debounceUpdateAdvanced()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
|
||||
const width = unref(realWidthRef)
|
||||
|
||||
const mdWidth =
|
||||
parseInt(itemCol.md as string) ||
|
||||
parseInt(itemCol.xs as string) ||
|
||||
parseInt(itemCol.sm as string) ||
|
||||
(itemCol.span as number) ||
|
||||
BASIC_COL_LEN
|
||||
|
||||
const lgWidth = parseInt(itemCol.lg as string) || mdWidth
|
||||
const xlWidth = parseInt(itemCol.xl as string) || lgWidth
|
||||
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth
|
||||
if (width <= screenEnum.LG) {
|
||||
itemColSum += mdWidth
|
||||
} else if (width < screenEnum.XL) {
|
||||
itemColSum += lgWidth
|
||||
} else if (width < screenEnum.XXL) {
|
||||
itemColSum += xlWidth
|
||||
} else {
|
||||
itemColSum += xxlWidth
|
||||
}
|
||||
|
||||
if (isLastAction) {
|
||||
advanceState.hideAdvanceBtn = false
|
||||
if (itemColSum <= BASIC_COL_LEN * 2) {
|
||||
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed
|
||||
advanceState.hideAdvanceBtn = true
|
||||
advanceState.isAdvanced = true
|
||||
} else if (
|
||||
itemColSum > BASIC_COL_LEN * 2 &&
|
||||
itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
|
||||
) {
|
||||
advanceState.hideAdvanceBtn = false
|
||||
|
||||
// More than 3 lines collapsed by default
|
||||
} else if (!advanceState.isLoad) {
|
||||
advanceState.isLoad = true
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced
|
||||
}
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum }
|
||||
}
|
||||
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) {
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum }
|
||||
} else {
|
||||
// The first line is always displayed
|
||||
return { isAdvanced: true, itemColSum }
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdvanced() {
|
||||
let itemColSum = 0
|
||||
let realItemColSum = 0
|
||||
const { baseColProps = {} } = unref(getProps)
|
||||
|
||||
for (const schema of unref(getSchema)) {
|
||||
const { show, colProps } = schema
|
||||
let isShow = true
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show
|
||||
}
|
||||
|
||||
if (isFunction(show)) {
|
||||
isShow = show({
|
||||
schema: schema,
|
||||
model: formModel,
|
||||
field: schema.field,
|
||||
values: {
|
||||
...unref(defaultValueRef),
|
||||
...formModel,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (isShow && (colProps || baseColProps)) {
|
||||
const { itemColSum: sum, isAdvanced } = getAdvanced(
|
||||
{ ...baseColProps, ...colProps },
|
||||
itemColSum,
|
||||
)
|
||||
|
||||
itemColSum = sum || 0
|
||||
if (isAdvanced) {
|
||||
realItemColSum = itemColSum
|
||||
}
|
||||
schema.isAdvanced = isAdvanced
|
||||
}
|
||||
}
|
||||
|
||||
// 确保页面发送更新
|
||||
vm?.proxy?.$forceUpdate()
|
||||
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan)
|
||||
|
||||
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true)
|
||||
|
||||
emit('advanced-change')
|
||||
}
|
||||
|
||||
function handleToggleAdvanced() {
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced
|
||||
}
|
||||
|
||||
return { handleToggleAdvanced }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormSchema, FormActionType, FormProps } from '../types/form';
|
||||
|
||||
import { unref, nextTick, watchEffect } from 'vue';
|
||||
|
||||
interface UseAutoFocusContext {
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
isInitedDefault: Ref<boolean>;
|
||||
formElRef: Ref<FormActionType>;
|
||||
}
|
||||
export async function useAutoFocus({
|
||||
getSchema,
|
||||
getProps,
|
||||
formElRef,
|
||||
isInitedDefault,
|
||||
}: UseAutoFocusContext) {
|
||||
watchEffect(async () => {
|
||||
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const schemas = unref(getSchema);
|
||||
const formEl = unref(formElRef);
|
||||
const el = (formEl as any)?.$el as HTMLElement;
|
||||
if (!formEl || !el || !schemas || schemas.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstItem = schemas[0];
|
||||
// Only open when the first form item is input type
|
||||
if (!firstItem.component.includes('Input')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
|
||||
if (!inputEl) return;
|
||||
inputEl?.focus();
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import type { ComponentType } from '../types/index';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { add, del } from '../componentMap';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export function useComponentRegister(compName: ComponentType, comp: Component) {
|
||||
add(compName, comp);
|
||||
tryOnUnmounted(() => {
|
||||
del(compName);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface'
|
||||
import type { DynamicProps } from '/#/utils'
|
||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue'
|
||||
import { isProdMode } from '/@/utils/env'
|
||||
import { error } from '/@/utils/log'
|
||||
import { getDynamicProps } from '/@/utils'
|
||||
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>
|
||||
|
||||
type Props = Partial<DynamicProps<FormProps>>
|
||||
|
||||
export function useForm(props?: Props): UseFormReturnType {
|
||||
const formRef = ref<Nullable<FormActionType>>(null)
|
||||
const loadedRef = ref<Nullable<boolean>>(false)
|
||||
|
||||
async function getForm() {
|
||||
const form = unref(formRef)
|
||||
if (!form) {
|
||||
error(
|
||||
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
|
||||
)
|
||||
}
|
||||
await nextTick()
|
||||
return form as FormActionType
|
||||
}
|
||||
|
||||
function register(instance: FormActionType) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
formRef.value = null
|
||||
loadedRef.value = null
|
||||
})
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return
|
||||
|
||||
formRef.value = instance
|
||||
loadedRef.value = true
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
() => {
|
||||
props && instance.setProps(getDynamicProps(props))
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
|
||||
const form = await getForm()
|
||||
form.scrollToField(name, options)
|
||||
},
|
||||
setProps: async (formProps: Partial<FormProps>) => {
|
||||
const form = await getForm()
|
||||
form.setProps(formProps)
|
||||
},
|
||||
|
||||
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm()
|
||||
form.updateSchema(data)
|
||||
},
|
||||
|
||||
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm()
|
||||
form.resetSchema(data)
|
||||
},
|
||||
|
||||
clearValidate: async (name?: string | string[]) => {
|
||||
const form = await getForm()
|
||||
form.clearValidate(name)
|
||||
},
|
||||
|
||||
resetFields: async () => {
|
||||
getForm().then(async (form) => {
|
||||
await form.resetFields()
|
||||
})
|
||||
},
|
||||
|
||||
removeSchemaByField: async (field: string | string[]) => {
|
||||
unref(formRef)?.removeSchemaByField(field)
|
||||
},
|
||||
|
||||
// TODO promisify
|
||||
getFieldsValue: <T>() => {
|
||||
return unref(formRef)?.getFieldsValue() as T
|
||||
},
|
||||
|
||||
setFieldsValue: async <T>(values: T) => {
|
||||
const form = await getForm()
|
||||
form.setFieldsValue<T>(values)
|
||||
},
|
||||
|
||||
appendSchemaByField: async (
|
||||
schema: FormSchema,
|
||||
prefixField: string | undefined,
|
||||
first: boolean,
|
||||
) => {
|
||||
const form = await getForm()
|
||||
form.appendSchemaByField(schema, prefixField, first)
|
||||
},
|
||||
|
||||
submit: async (): Promise<any> => {
|
||||
const form = await getForm()
|
||||
return form.submit()
|
||||
},
|
||||
|
||||
validate: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm()
|
||||
return form.validate(nameList)
|
||||
},
|
||||
|
||||
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm()
|
||||
return form.validateFields(nameList)
|
||||
},
|
||||
}
|
||||
|
||||
return [register, methods]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import type { InjectionKey } from 'vue';
|
||||
import { createContext, useContext } from '/@/hooks/core/useContext';
|
||||
|
||||
export interface FormContextProps {
|
||||
resetAction: () => Promise<void>;
|
||||
submitAction: () => Promise<void>;
|
||||
}
|
||||
|
||||
const key: InjectionKey<FormContextProps> = Symbol();
|
||||
|
||||
export function createFormContext(context: FormContextProps) {
|
||||
return createContext<FormContextProps>(context, key);
|
||||
}
|
||||
|
||||
export function useFormContext() {
|
||||
return useContext<FormContextProps>(key);
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { FormProps, FormSchema, FormActionType } from '../types/form'
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface'
|
||||
import { unref, toRaw, nextTick } from 'vue'
|
||||
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'
|
||||
import { deepMerge } from '/@/utils'
|
||||
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { cloneDeep, uniqBy } from 'lodash-es'
|
||||
import { error } from '/@/utils/log'
|
||||
|
||||
interface UseFormActionContext {
|
||||
emit: EmitType
|
||||
getProps: ComputedRef<FormProps>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
formModel: Recordable
|
||||
defaultValueRef: Ref<Recordable>
|
||||
formElRef: Ref<FormActionType>
|
||||
schemaRef: Ref<FormSchema[]>
|
||||
handleFormValues: Fn
|
||||
}
|
||||
export function useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
defaultValueRef,
|
||||
formElRef,
|
||||
schemaRef,
|
||||
handleFormValues,
|
||||
}: UseFormActionContext) {
|
||||
async function resetFields(): Promise<void> {
|
||||
const { resetFunc, submitOnReset } = unref(getProps)
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc())
|
||||
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return
|
||||
|
||||
Object.keys(formModel).forEach((key) => {
|
||||
const schema = unref(getSchema).find((item) => item.field === key)
|
||||
const isInput = schema?.component && defaultValueComponents.includes(schema.component)
|
||||
const defaultValue = cloneDeep(defaultValueRef.value[key])
|
||||
formModel[key] = isInput ? defaultValue || '' : defaultValue
|
||||
})
|
||||
nextTick(() => clearValidate())
|
||||
|
||||
emit('reset', toRaw(formModel))
|
||||
submitOnReset && handleSubmit()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Set form value
|
||||
*/
|
||||
async function setFieldsValue(values: Recordable): Promise<void> {
|
||||
const fields = unref(getSchema)
|
||||
.map((item) => item.field)
|
||||
.filter(Boolean)
|
||||
|
||||
// key 支持 a.b.c 的嵌套写法
|
||||
const delimiter = '.'
|
||||
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0)
|
||||
|
||||
const validKeys: string[] = []
|
||||
Object.keys(values).forEach((key) => {
|
||||
const schema = unref(getSchema).find((item) => item.field === key)
|
||||
let value = values[key]
|
||||
|
||||
const hasKey = Reflect.has(values, key)
|
||||
|
||||
value = handleInputNumberValue(schema?.component, value)
|
||||
// 0| '' is allow
|
||||
if (hasKey && fields.includes(key)) {
|
||||
// time type
|
||||
if (itemIsDateType(key)) {
|
||||
if (Array.isArray(value)) {
|
||||
const arr: any[] = []
|
||||
for (const ele of value) {
|
||||
arr.push(ele ? dateUtil(ele) : null)
|
||||
}
|
||||
formModel[key] = arr
|
||||
} else {
|
||||
const { componentProps } = schema || {}
|
||||
let _props = componentProps as any
|
||||
if (typeof componentProps === 'function') {
|
||||
_props = _props({ formModel })
|
||||
}
|
||||
formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null
|
||||
}
|
||||
} else {
|
||||
formModel[key] = value
|
||||
}
|
||||
validKeys.push(key)
|
||||
} else {
|
||||
nestKeyArray.forEach((nestKey: string) => {
|
||||
try {
|
||||
const value = eval('values' + delimiter + nestKey)
|
||||
if (isDef(value)) {
|
||||
formModel[nestKey] = value
|
||||
validKeys.push(nestKey)
|
||||
}
|
||||
} catch (e) {
|
||||
// key not exist
|
||||
if (isDef(defaultValueRef.value[nestKey])) {
|
||||
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
validateFields(validKeys).catch((_) => {})
|
||||
}
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
async function removeSchemaByField(fields: string | string[]): Promise<void> {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
|
||||
if (!fields) {
|
||||
return
|
||||
}
|
||||
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields]
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
_removeSchemaByFeild(field, schemaList)
|
||||
}
|
||||
schemaRef.value = schemaList
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void {
|
||||
if (isString(field)) {
|
||||
const index = schemaList.findIndex((schema) => schema.field === field)
|
||||
if (index !== -1) {
|
||||
delete formModel[field]
|
||||
schemaList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Insert after a certain field, if not insert the last
|
||||
*/
|
||||
async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField)
|
||||
|
||||
if (!prefixField || index === -1 || first) {
|
||||
first ? schemaList.unshift(schema) : schemaList.push(schema)
|
||||
schemaRef.value = schemaList
|
||||
_setDefaultValue(schema)
|
||||
return
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, schema)
|
||||
}
|
||||
_setDefaultValue(schema)
|
||||
|
||||
schemaRef.value = schemaList
|
||||
}
|
||||
|
||||
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = []
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema)
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data]
|
||||
}
|
||||
|
||||
const hasField = updateData.every(
|
||||
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
|
||||
)
|
||||
|
||||
if (!hasField) {
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
)
|
||||
return
|
||||
}
|
||||
schemaRef.value = updateData as FormSchema[]
|
||||
}
|
||||
|
||||
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = []
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema)
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data]
|
||||
}
|
||||
|
||||
const hasField = updateData.every(
|
||||
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
|
||||
)
|
||||
|
||||
if (!hasField) {
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
)
|
||||
return
|
||||
}
|
||||
const schema: FormSchema[] = []
|
||||
updateData.forEach((item) => {
|
||||
unref(getSchema).forEach((val) => {
|
||||
if (val.field === item.field) {
|
||||
const newSchema = deepMerge(val, item)
|
||||
schema.push(newSchema as FormSchema)
|
||||
} else {
|
||||
schema.push(val)
|
||||
}
|
||||
})
|
||||
})
|
||||
_setDefaultValue(schema)
|
||||
|
||||
schemaRef.value = uniqBy(schema, 'field')
|
||||
}
|
||||
|
||||
function _setDefaultValue(data: FormSchema | FormSchema[]) {
|
||||
let schemas: FormSchema[] = []
|
||||
if (isObject(data)) {
|
||||
schemas.push(data as FormSchema)
|
||||
}
|
||||
if (isArray(data)) {
|
||||
schemas = [...data]
|
||||
}
|
||||
|
||||
const obj: Recordable = {}
|
||||
const currentFieldsValue = getFieldsValue()
|
||||
schemas.forEach((item) => {
|
||||
if (
|
||||
item.component != 'Divider' &&
|
||||
Reflect.has(item, 'field') &&
|
||||
item.field &&
|
||||
!isNullOrUnDef(item.defaultValue) &&
|
||||
!(item.field in currentFieldsValue)
|
||||
) {
|
||||
obj[item.field] = item.defaultValue
|
||||
}
|
||||
})
|
||||
setFieldsValue(obj)
|
||||
}
|
||||
|
||||
function getFieldsValue(): Recordable {
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return {}
|
||||
return handleFormValues(toRaw(unref(formModel)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Is it time
|
||||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some((item) => {
|
||||
return item.field === key ? dateItemType.includes(item.component) : false
|
||||
})
|
||||
}
|
||||
|
||||
async function validateFields(nameList?: NamePath[] | undefined) {
|
||||
return unref(formElRef)?.validateFields(nameList)
|
||||
}
|
||||
|
||||
async function validate(nameList?: NamePath[] | undefined) {
|
||||
return await unref(formElRef)?.validate(nameList)
|
||||
}
|
||||
|
||||
async function clearValidate(name?: string | string[]) {
|
||||
await unref(formElRef)?.clearValidate(name)
|
||||
}
|
||||
|
||||
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
|
||||
await unref(formElRef)?.scrollToField(name, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Form submission
|
||||
*/
|
||||
async function handleSubmit(e?: Event): Promise<void> {
|
||||
e && e.preventDefault()
|
||||
const { submitFunc } = unref(getProps)
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc()
|
||||
return
|
||||
}
|
||||
const formEl = unref(formElRef)
|
||||
if (!formEl) return
|
||||
try {
|
||||
const values = await validate()
|
||||
const res = handleFormValues(values)
|
||||
emit('submit', res)
|
||||
} catch (error: any) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
clearValidate,
|
||||
validate,
|
||||
validateFields,
|
||||
getFieldsValue,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
appendSchemaByField,
|
||||
removeSchemaByField,
|
||||
resetFields,
|
||||
setFieldsValue,
|
||||
scrollToField,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'
|
||||
import { dateUtil } from '/@/utils/dateUtil'
|
||||
import { unref } from 'vue'
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { cloneDeep, set } from 'lodash-es'
|
||||
|
||||
interface UseFormValuesContext {
|
||||
defaultValueRef: Ref<any>
|
||||
getSchema: ComputedRef<FormSchema[]>
|
||||
getProps: ComputedRef<FormProps>
|
||||
formModel: Recordable
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription deconstruct array-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructArray(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\[(.+)\]$/
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern)
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',')
|
||||
value = Array.isArray(value) ? value : [value]
|
||||
keys.forEach((k, index) => {
|
||||
set(target, k.trim(), value[index])
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription deconstruct object-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructObject(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\{(.+)\}$/
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern)
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',')
|
||||
value = isObject(value) ? value : {}
|
||||
keys.forEach((k) => {
|
||||
set(target, k.trim(), value[k.trim()])
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormValues({
|
||||
defaultValueRef,
|
||||
getSchema,
|
||||
formModel,
|
||||
getProps,
|
||||
}: UseFormValuesContext) {
|
||||
// Processing form values
|
||||
function handleFormValues(values: Recordable) {
|
||||
if (!isObject(values)) {
|
||||
return {}
|
||||
}
|
||||
const res: Recordable = {}
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item
|
||||
const [key] = item
|
||||
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
|
||||
continue
|
||||
}
|
||||
const transformDateFunc = unref(getProps).transformDateFunc
|
||||
if (isObject(value)) {
|
||||
value = transformDateFunc?.(value)
|
||||
}
|
||||
|
||||
if (isArray(value) && value[0]?.format && value[1]?.format) {
|
||||
value = value.map((item) => transformDateFunc?.(item))
|
||||
}
|
||||
// Remove spaces
|
||||
if (isString(value)) {
|
||||
value = value.trim()
|
||||
}
|
||||
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
|
||||
// 没有解构成功的,按原样赋值
|
||||
set(res, key, value)
|
||||
}
|
||||
}
|
||||
return handleRangeTimeValue(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Processing time interval parameters
|
||||
*/
|
||||
function handleRangeTimeValue(values: Recordable) {
|
||||
const fieldMapToTime = unref(getProps).fieldMapToTime
|
||||
|
||||
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
|
||||
return values
|
||||
}
|
||||
|
||||
for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
|
||||
if (!field || !startTimeKey || !endTimeKey || !values[field]) {
|
||||
continue
|
||||
}
|
||||
|
||||
const [startTime, endTime]: string[] = values[field]
|
||||
|
||||
values[startTimeKey] = dateUtil(startTime).format(format)
|
||||
values[endTimeKey] = dateUtil(endTime).format(format)
|
||||
Reflect.deleteProperty(values, field)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
function initDefault() {
|
||||
const schemas = unref(getSchema)
|
||||
const obj: Recordable = {}
|
||||
schemas.forEach((item) => {
|
||||
const { defaultValue } = item
|
||||
if (!isNullOrUnDef(defaultValue)) {
|
||||
obj[item.field] = defaultValue
|
||||
|
||||
if (formModel[item.field] === undefined) {
|
||||
formModel[item.field] = defaultValue
|
||||
}
|
||||
}
|
||||
})
|
||||
defaultValueRef.value = cloneDeep(obj)
|
||||
}
|
||||
|
||||
return { handleFormValues, initDefault }
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import type { Ref } from 'vue'
|
||||
import { computed, unref } from 'vue'
|
||||
import type { FormProps, FormSchema } from '../types/form'
|
||||
import { isNumber } from '/@/utils/is'
|
||||
|
||||
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
||||
return computed(() => {
|
||||
const schemaItem = unref(schemaItemRef)
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem
|
||||
|
||||
const {
|
||||
labelWidth: globalLabelWidth,
|
||||
labelCol: globalLabelCol,
|
||||
wrapperCol: globWrapperCol,
|
||||
layout,
|
||||
} = unref(propsRef)
|
||||
|
||||
// If labelWidth is set globally, all items setting
|
||||
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
|
||||
labelCol.style = {
|
||||
textAlign: 'left',
|
||||
}
|
||||
return { labelCol, wrapperCol }
|
||||
}
|
||||
let width = labelWidth || globalLabelWidth
|
||||
const col = { ...globalLabelCol, ...labelCol }
|
||||
const wrapCol = { ...globWrapperCol, ...wrapperCol }
|
||||
|
||||
if (width) {
|
||||
width = isNumber(width) ? `${width}px` : width
|
||||
}
|
||||
|
||||
return {
|
||||
labelCol: { style: { width }, ...col },
|
||||
wrapperCol: {
|
||||
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
|
||||
...wrapCol,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import type { FieldMapToTime, FormSchema } from './types/form'
|
||||
import type { CSSProperties, PropType } from 'vue'
|
||||
import type { ColEx } from './types'
|
||||
import type { TableActionType } from '/@/components/Table'
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
|
||||
export const basicProps = {
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: {},
|
||||
},
|
||||
// 标签宽度 固定宽度
|
||||
labelWidth: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 0,
|
||||
},
|
||||
fieldMapToTime: {
|
||||
type: Array as PropType<FieldMapToTime>,
|
||||
default: () => [],
|
||||
},
|
||||
compact: propTypes.bool,
|
||||
// 表单配置规则
|
||||
schemas: {
|
||||
type: [Array] as PropType<FormSchema[]>,
|
||||
default: () => [],
|
||||
},
|
||||
mergeDynamicData: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: null,
|
||||
},
|
||||
baseRowStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
baseColProps: {
|
||||
type: Object as PropType<Partial<ColEx>>,
|
||||
},
|
||||
autoSetPlaceHolder: propTypes.bool.def(true),
|
||||
// 在INPUT组件上单击回车时,是否自动提交
|
||||
autoSubmitOnEnter: propTypes.bool.def(false),
|
||||
submitOnReset: propTypes.bool,
|
||||
submitOnChange: propTypes.bool,
|
||||
size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
|
||||
// 禁用表单
|
||||
disabled: propTypes.bool,
|
||||
emptySpan: {
|
||||
type: [Number, Object] as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
// 是否显示收起展开按钮
|
||||
showAdvancedButton: propTypes.bool,
|
||||
// 转化时间
|
||||
transformDateFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: (date: any) => {
|
||||
return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date
|
||||
},
|
||||
},
|
||||
rulesMessageJoinLabel: propTypes.bool.def(true),
|
||||
// 超过3行自动折叠
|
||||
autoAdvancedLine: propTypes.number.def(3),
|
||||
// 不受折叠影响的行数
|
||||
alwaysShowLines: propTypes.number.def(1),
|
||||
|
||||
// 是否显示操作按钮
|
||||
showActionButtonGroup: propTypes.bool.def(true),
|
||||
// 操作列Col配置
|
||||
actionColOptions: Object as PropType<Partial<ColEx>>,
|
||||
// 显示重置按钮
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
// 是否聚焦第一个输入框,只在第一个表单项为input的时候作用
|
||||
autoFocusFirstItem: propTypes.bool,
|
||||
// 重置按钮配置
|
||||
resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 显示确认按钮
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 自定义重置函数
|
||||
resetFunc: Function as PropType<() => Promise<void>>,
|
||||
submitFunc: Function as PropType<() => Promise<void>>,
|
||||
|
||||
// 以下为默认props
|
||||
hideRequiredMark: propTypes.bool,
|
||||
|
||||
labelCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
layout: propTypes.oneOf(['horizontal', 'vertical', 'inline']).def('horizontal'),
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
|
||||
wrapperCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
colon: propTypes.bool,
|
||||
|
||||
labelAlign: propTypes.string,
|
||||
|
||||
rowProps: Object as PropType<RowProps>,
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'
|
||||
import type { VNode } from 'vue'
|
||||
import type { ButtonProps as AntdButtonProps } from '/@/components/Button'
|
||||
import type { FormItem } from './formItem'
|
||||
import type { ColEx, ComponentType } from './index'
|
||||
import type { TableActionType } from '/@/components/Table/src/types/table'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
|
||||
|
||||
export type FieldMapToTime = [string, [string, string], string?][]
|
||||
|
||||
export type Rule = RuleObject & {
|
||||
trigger?: 'blur' | 'change' | ['change', 'blur']
|
||||
}
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema
|
||||
values: Recordable
|
||||
model: Recordable
|
||||
field: string
|
||||
}
|
||||
|
||||
export interface ButtonProps extends AntdButtonProps {
|
||||
text?: string
|
||||
}
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<void>
|
||||
setFieldsValue: <T>(values: T) => Promise<void>
|
||||
resetFields: () => Promise<void>
|
||||
getFieldsValue: () => Recordable
|
||||
clearValidate: (name?: string | string[]) => Promise<void>
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
|
||||
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>
|
||||
removeSchemaByField: (field: string | string[]) => Promise<void>
|
||||
appendSchemaByField: (
|
||||
schema: FormSchema,
|
||||
prefixField: string | undefined,
|
||||
first?: boolean | undefined,
|
||||
) => Promise<void>
|
||||
validateFields: (nameList?: NamePath[]) => Promise<any>
|
||||
validate: (nameList?: NamePath[]) => Promise<any>
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType]
|
||||
|
||||
export interface FormProps {
|
||||
name?: string
|
||||
layout?: 'vertical' | 'inline' | 'horizontal'
|
||||
// Form value
|
||||
model?: Recordable
|
||||
// The width of all items in the entire form
|
||||
labelWidth?: number | string
|
||||
// alignment
|
||||
labelAlign?: 'left' | 'right'
|
||||
// Row configuration for the entire form
|
||||
rowProps?: RowProps
|
||||
// Submit form on reset
|
||||
submitOnReset?: boolean
|
||||
// Submit form on form changing
|
||||
submitOnChange?: boolean
|
||||
// Col configuration for the entire form
|
||||
labelCol?: Partial<ColEx>
|
||||
// Col configuration for the entire form
|
||||
wrapperCol?: Partial<ColEx>
|
||||
|
||||
// General row style
|
||||
baseRowStyle?: CSSProperties
|
||||
|
||||
// General col configuration
|
||||
baseColProps?: Partial<ColEx>
|
||||
|
||||
// Form configuration rules
|
||||
schemas?: FormSchema[]
|
||||
// Function values used to merge into dynamic control form items
|
||||
mergeDynamicData?: Recordable
|
||||
// Compact mode for search forms
|
||||
compact?: boolean
|
||||
// Blank line span
|
||||
emptySpan?: number | Partial<ColEx>
|
||||
// Internal component size of the form
|
||||
size?: 'default' | 'small' | 'large'
|
||||
// Whether to disable
|
||||
disabled?: boolean
|
||||
// Time interval fields are mapped into multiple
|
||||
fieldMapToTime?: FieldMapToTime
|
||||
// Placeholder is set automatically
|
||||
autoSetPlaceHolder?: boolean
|
||||
// Auto submit on press enter on input
|
||||
autoSubmitOnEnter?: boolean
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean
|
||||
// Whether to show collapse and expand buttons
|
||||
showAdvancedButton?: boolean
|
||||
// Whether to focus on the first input box, only works when the first form item is input
|
||||
autoFocusFirstItem?: boolean
|
||||
// Automatically collapse over the specified number of rows
|
||||
autoAdvancedLine?: number
|
||||
// Always show lines
|
||||
alwaysShowLines?: number
|
||||
// Whether to show the operation button
|
||||
showActionButtonGroup?: boolean
|
||||
|
||||
// Reset button configuration
|
||||
resetButtonOptions?: Partial<ButtonProps>
|
||||
|
||||
// Confirm button configuration
|
||||
submitButtonOptions?: Partial<ButtonProps>
|
||||
|
||||
// Operation column configuration
|
||||
actionColOptions?: Partial<ColEx>
|
||||
|
||||
// Show reset button
|
||||
showResetButton?: boolean
|
||||
// Show confirmation button
|
||||
showSubmitButton?: boolean
|
||||
|
||||
resetFunc?: () => Promise<void>
|
||||
submitFunc?: () => Promise<void>
|
||||
transformDateFunc?: (date: any) => string
|
||||
colon?: boolean
|
||||
}
|
||||
export interface FormSchema {
|
||||
// Field name
|
||||
field: string
|
||||
// Event name triggered by internal value change, default change
|
||||
changeEvent?: string
|
||||
// Variable name bound to v-model Default value
|
||||
valueField?: string
|
||||
// Label name
|
||||
label: string | VNode
|
||||
// Auxiliary text
|
||||
subLabel?: string
|
||||
// Help text on the right side of the text
|
||||
helpMessage?:
|
||||
| string
|
||||
| string[]
|
||||
| ((renderCallbackParams: RenderCallbackParams) => string | string[])
|
||||
// BaseHelp component props
|
||||
helpComponentProps?: Partial<HelpComponentProps>
|
||||
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
|
||||
labelWidth?: string | number
|
||||
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
|
||||
disabledLabelWidth?: boolean
|
||||
// render component
|
||||
component: ComponentType
|
||||
// Component parameters
|
||||
componentProps?:
|
||||
| ((opt: {
|
||||
schema: FormSchema
|
||||
tableAction: TableActionType
|
||||
formActionType: FormActionType
|
||||
formModel: Recordable
|
||||
}) => Recordable)
|
||||
| object
|
||||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number)
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[]
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean
|
||||
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any
|
||||
isAdvanced?: boolean
|
||||
|
||||
// Matching details components
|
||||
span?: number
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
|
||||
// Render the content in the form-item tag
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
|
||||
|
||||
// Rendering col content requires outer wrapper form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
|
||||
|
||||
renderComponentContent?:
|
||||
| ((renderCallbackParams: RenderCallbackParams) => any)
|
||||
| VNode
|
||||
| VNode[]
|
||||
| string
|
||||
|
||||
// Custom slot, in from-item
|
||||
slot?: string
|
||||
|
||||
// Custom slot, similar to renderColContent
|
||||
colSlot?: string
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
|
||||
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string
|
||||
// Whether to display the serial number
|
||||
showIndex: boolean
|
||||
// Text list
|
||||
text: any
|
||||
// colour
|
||||
color: string
|
||||
// font size
|
||||
fontSize: string
|
||||
icon: string
|
||||
absolute: boolean
|
||||
// Positioning
|
||||
position: any
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import type { ColProps } from 'ant-design-vue/lib/grid/Col';
|
||||
import type { HTMLAttributes, VNodeChild } from 'vue';
|
||||
|
||||
export interface FormItem {
|
||||
/**
|
||||
* Used with label, whether to display : after label text.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
colon?: boolean;
|
||||
|
||||
/**
|
||||
* The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
extra?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Used with validateStatus, this option specifies the validation status icon. Recommended to be used only with Input.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
hasFeedback?: boolean;
|
||||
|
||||
/**
|
||||
* The prompt message. If not provided, the prompt message will be generated by the validation rule.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
help?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Label test
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
label?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col>
|
||||
* @type Col
|
||||
*/
|
||||
labelCol?: ColProps & HTMLAttributes;
|
||||
|
||||
/**
|
||||
* Whether provided or not, it will be generated by the validation rule.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
required?: boolean;
|
||||
|
||||
/**
|
||||
* The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating'
|
||||
* @type string
|
||||
*/
|
||||
validateStatus?: '' | 'success' | 'warning' | 'error' | 'validating';
|
||||
|
||||
/**
|
||||
* The layout for input controls, same as labelCol
|
||||
* @type Col
|
||||
*/
|
||||
wrapperCol?: ColProps;
|
||||
/**
|
||||
* Set sub label htmlFor.
|
||||
*/
|
||||
htmlFor?: string;
|
||||
/**
|
||||
* text align of label
|
||||
*/
|
||||
labelAlign?: 'left' | 'right';
|
||||
/**
|
||||
* a key of model. In the setting of validate and resetFields method, the attribute is required
|
||||
*/
|
||||
name?: NamePath;
|
||||
/**
|
||||
* validation rules of form
|
||||
*/
|
||||
rules?: object | object[];
|
||||
/**
|
||||
* Whether to automatically associate form fields. In most cases, you can setting automatic association.
|
||||
* If the conditions for automatic association are not met, you can manually associate them. See the notes below.
|
||||
*/
|
||||
autoLink?: boolean;
|
||||
/**
|
||||
* Whether stop validate on first rule of error for this field.
|
||||
*/
|
||||
validateFirst?: boolean;
|
||||
/**
|
||||
* When to validate the value of children node
|
||||
*/
|
||||
validateTrigger?: string | string[] | false;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface AdvanceState {
|
||||
isAdvanced: boolean;
|
||||
hideAdvanceBtn: boolean;
|
||||
isLoad: boolean;
|
||||
actionSpan: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
type ColSpanType = number | string
|
||||
export interface ColEx {
|
||||
style?: any
|
||||
/**
|
||||
* raster number of cells to occupy, 0 corresponds to display: none
|
||||
* @default none (0)
|
||||
* @type ColSpanType
|
||||
*/
|
||||
span?: ColSpanType
|
||||
|
||||
/**
|
||||
* raster order, used in flex layout mode
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
order?: ColSpanType
|
||||
|
||||
/**
|
||||
* the layout fill of flex
|
||||
* @default none
|
||||
* @type ColSpanType
|
||||
*/
|
||||
flex?: ColSpanType
|
||||
|
||||
/**
|
||||
* the number of cells to offset Col from the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
offset?: ColSpanType
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the right
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
push?: ColSpanType
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
pull?: ColSpanType
|
||||
|
||||
/**
|
||||
* <576px and also default setting, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
|
||||
/**
|
||||
* ≥576px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
|
||||
/**
|
||||
* ≥768px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
|
||||
/**
|
||||
* ≥992px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
|
||||
/**
|
||||
* ≥1200px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
|
||||
/**
|
||||
* ≥1600px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
|
||||
}
|
||||
|
||||
export type ComponentType =
|
||||
| 'Input'
|
||||
| 'InputGroup'
|
||||
| 'InputPassword'
|
||||
| 'InputSearch'
|
||||
| 'InputTextArea'
|
||||
| 'InputNumber'
|
||||
| 'InputCountDown'
|
||||
| 'Select'
|
||||
| 'ApiSelect'
|
||||
| 'TreeSelect'
|
||||
| 'ApiTree'
|
||||
| 'ApiTreeSelect'
|
||||
| 'ApiRadioGroup'
|
||||
| 'RadioButtonGroup'
|
||||
| 'RadioGroup'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'AutoComplete'
|
||||
| 'ApiCascader'
|
||||
| 'Cascader'
|
||||
| 'DatePicker'
|
||||
| 'MonthPicker'
|
||||
| 'RangePicker'
|
||||
| 'WeekPicker'
|
||||
| 'TimePicker'
|
||||
| 'Switch'
|
||||
| 'StrengthMeter'
|
||||
| 'Upload'
|
||||
| 'IconPicker'
|
||||
| 'Render'
|
||||
| 'Slider'
|
||||
| 'Rate'
|
||||
| 'Divider'
|
||||
| 'ApiTransfer'
|
||||
|
|
@ -7,5 +7,5 @@ export * from './src/types/table'
|
|||
export * from './src/types/pagination'
|
||||
export * from './src/types/tableAction'
|
||||
export { useTable } from './src/hooks/useTable'
|
||||
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'
|
||||
export type { FormSchema, FormProps } from '../Form1/src/types/form'
|
||||
export type { EditRecordRow } from './src/components/editable'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
Radio,
|
||||
} from 'ant-design-vue'
|
||||
import type { ComponentType } from './types/componentType'
|
||||
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form'
|
||||
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '../../Form1'
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'
|
||||
import type { PaginationProps } from '../types/pagination'
|
||||
import type { DynamicProps } from '/#/utils'
|
||||
import type { FormActionType } from '/@/components/Form'
|
||||
import type { FormActionType } from '../../../Form1'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import { getDynamicProps } from '/@/utils'
|
||||
import { ref, onUnmounted, unref, watch, toRaw } from 'vue'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ComputedRef, Slots } from 'vue';
|
||||
import type { BasicTableProps, FetchParams } from '../types/table';
|
||||
import { unref, computed } from 'vue';
|
||||
import type { FormProps } from '/@/components/Form';
|
||||
import type { FormProps } from '../../../Form1';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
export function useTableForm(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import type {
|
|||
TableRowSelection,
|
||||
SizeType,
|
||||
} from './types/table'
|
||||
import type { FormProps } from '/@/components/Form'
|
||||
import type { FormProps } from '../../Form1'
|
||||
|
||||
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
|
||||
import { propTypes } from '/@/utils/propTypes'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { VNodeChild } from 'vue'
|
||||
import type { PaginationProps } from './pagination'
|
||||
import type { FormProps } from '/@/components/Form'
|
||||
import type { FormProps } from '../../../Form1'
|
||||
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'
|
||||
import type { ColumnProps } from 'ant-design-vue/lib/table'
|
||||
|
||||
|
|
|
|||
|
|
@ -48,11 +48,9 @@ export function useRuleFormItem<T extends Recordable>(
|
|||
},
|
||||
set(value) {
|
||||
if (isEqual(value, defaultState.value)) return
|
||||
|
||||
innerState.value = value as T[keyof T]
|
||||
nextTick(() => {
|
||||
emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []))
|
||||
})
|
||||
emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []))
|
||||
// nextTick(() => {})
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
width="500px"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<BasicForm @register="registerForm"> </BasicForm>
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -57,6 +57,40 @@
|
|||
return object
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
// 根据老系统快速添加
|
||||
// const obj = {}
|
||||
|
||||
// obj?.result?.records?.forEach(async (e) => {
|
||||
// const { bigdataCameraParams } = e
|
||||
// const params = {
|
||||
// type: 1,
|
||||
// agricultural_base_id: 26,
|
||||
// sn: e.number,
|
||||
// monitoring_point: e.monitoringPoint,
|
||||
// extends: {
|
||||
// ip: bigdataCameraParams.ip,
|
||||
// port: bigdataCameraParams.port + '',
|
||||
// rtsp_url: bigdataCameraParams.url,
|
||||
// username: bigdataCameraParams.username,
|
||||
// password: bigdataCameraParams.password,
|
||||
// passage: bigdataCameraParams.nchannelId + '',
|
||||
// },
|
||||
// }
|
||||
|
||||
// try {
|
||||
// await createDevice(params)
|
||||
// } catch (e) {
|
||||
// console.log(params)
|
||||
|
||||
// console.log('错误')
|
||||
|
||||
// console.log(e)
|
||||
// }
|
||||
// })
|
||||
// // const values = await validate()
|
||||
|
||||
// return
|
||||
|
||||
try {
|
||||
const values = await validate()
|
||||
let params = {}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { BasicColumn, FormSchema } from '/@/components/Table'
|
|||
import dayjs from 'dayjs'
|
||||
import { h } from 'vue'
|
||||
import { Tag } from 'ant-design-vue'
|
||||
import { ColEx } from '/@/components/Form/src/types'
|
||||
import { ColEx } from '../../../components/Form1/src/types'
|
||||
// import { formatDataByObject } from '/@/utils/index'
|
||||
// import { getGriculturalDeviceBasic, getaGriculturalDevicePoint } from '/@/api/sys/user'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<BasicForm @register="registerForm" />
|
||||
<List
|
||||
:grid="{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 4, xxl: 5, column: 1 }"
|
||||
:grid="{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 3, xl: 4, xxl: 5, column: 8 }"
|
||||
:data-source="list"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
return formatDataByObject(resData)
|
||||
},
|
||||
params: {
|
||||
device_type: 4,
|
||||
device_type: 1,
|
||||
agricultural_basic: e,
|
||||
},
|
||||
labelField: 'label',
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="">{{ item.base_name }} </div>
|
||||
<div class="">{{ item.base_name }}-{{ item.monitoring_point }} </div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Tag } from 'ant-design-vue'
|
|||
import { Switch } from 'ant-design-vue'
|
||||
import { useMessage } from '/@/hooks/web/useMessage'
|
||||
import { updateFriendinks } from '/@/api/sys/user'
|
||||
import { ColEx } from '/@/components/Form/src/types'
|
||||
import { ColEx } from '../../../components/Form1/src/types'
|
||||
import { Tinymce } from '/@/components/Tinymce/index'
|
||||
import { uploadApi } from '/@/api/upload'
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
const obj = formatChartData()
|
||||
|
||||
setOptions({
|
||||
grid: { left: '2%', right: '4%', top: '10%', bottom: '2%', containLabel: true },
|
||||
grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
|
||||
legend: {
|
||||
data: obj.legendData,
|
||||
top: '0%',
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
// const max = Math.max(...largestOfFour(Data.series)) ?? 100
|
||||
|
||||
setOptions({
|
||||
grid: { left: '4%', right: '4%', top: '10%', bottom: '2%', containLabel: true },
|
||||
grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
|
||||
legend: {
|
||||
data: obj.legendData,
|
||||
top: '0%',
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
const chartsInit = () => {
|
||||
const obj = formatChartData()
|
||||
setOptions({
|
||||
grid: { left: '2%', right: '2%', top: '10%', bottom: '2%', containLabel: true },
|
||||
grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
|
||||
legend: {
|
||||
data: obj.legendData,
|
||||
top: '0%',
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@
|
|||
}
|
||||
})
|
||||
setOptions({
|
||||
grid: { left: '2%', right: '2%', top: '30px', bottom: '2%', containLabel: true },
|
||||
grid: { left: '2%', right: '20px', top: '30px', bottom: '2%', containLabel: true },
|
||||
legend: {
|
||||
data: data.map((e) => e.name),
|
||||
top: '0%',
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@
|
|||
})
|
||||
|
||||
setOptions({
|
||||
grid: { left: '2%', right: '2%', top: '30px', bottom: '2%', containLabel: true },
|
||||
grid: { left: '2%', right: '20px', top: '30px', bottom: '2%', containLabel: true },
|
||||
legend: {
|
||||
data: data.map((e) => e.name),
|
||||
top: '0%',
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
import { computed } from '@vue/reactivity'
|
||||
// import Am from './components/Star'
|
||||
const initTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
// const initTime = '2022-10-20 00:00'
|
||||
localStorage.removeItem('warning_id')
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
|
@ -142,13 +143,13 @@
|
|||
localStorage.setItem('warning_id', arr.join(','))
|
||||
|
||||
fliterData.forEach((e, index) => {
|
||||
openNotificationWithIcon(e.content, 4.5 + index * 0.5)
|
||||
openNotificationWithIcon(e, 10 + index * 1)
|
||||
})
|
||||
}
|
||||
|
||||
const openNotificationWithIcon = (message: string, duration = 4.5) => {
|
||||
const openNotificationWithIcon = (message, duration = 4.5) => {
|
||||
notification.warning({
|
||||
message: '报警',
|
||||
message: message.base_name,
|
||||
duration: duration,
|
||||
class: 'warning-class',
|
||||
style: {
|
||||
|
|
@ -159,7 +160,7 @@
|
|||
if (document.body.clientWidth < 3000) return document.body
|
||||
return document.body.querySelector(`.visualization—xx`)
|
||||
},
|
||||
description: message,
|
||||
description: message.point_name + ':' + message.content,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -234,6 +235,10 @@
|
|||
.ant-notification-notice-message {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.anticon-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="less">
|
||||
|
|
|
|||
Loading…
Reference in New Issue