new-map
ihzero 2022-11-23 18:43:09 +08:00
parent 13f58ed486
commit 591a49805c
61 changed files with 4040 additions and 697 deletions

View File

@ -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/form';
export * from './src/types/formItem' export * from './src/types/formItem';
export { useComponentRegister } from './src/hooks/useComponentRegister' export { useComponentRegister } from './src/hooks/useComponentRegister';
export { useForm } from './src/hooks/useForm' export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue' export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue' export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue' export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
export { default as ApiTree } from './src/components/ApiTree.vue' export { default as ApiTree } from './src/components/ApiTree.vue';
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue' export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { default as ApiCascader } from './src/components/ApiCascader.vue' export { default as ApiCascader } from './src/components/ApiCascader.vue';
export { default as ApiTransfer } from './src/components/ApiTransfer.vue' export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
export { BasicForm } export { BasicForm };

View File

@ -10,6 +10,7 @@
<slot name="formHeader"></slot> <slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field"> <template v-for="schema in getSchema" :key="schema.field">
<FormItem <FormItem
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
:tableAction="tableAction" :tableAction="tableAction"
:formActionType="formActionType" :formActionType="formActionType"
:schema="schema" :schema="schema"
@ -118,9 +119,9 @@
const getSchema = computed((): FormSchema[] => { const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any) const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
for (const schema of schemas) { for (const schema of schemas) {
const { defaultValue, component } = schema const { defaultValue, component, isHandleDateDefaultValue = true } = schema
// handle date type // handle date type
if (defaultValue && dateItemType.includes(component)) { if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
if (!Array.isArray(defaultValue)) { if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue) schema.defaultValue = dateUtil(defaultValue)
} else { } else {
@ -141,7 +142,7 @@
} }
}) })
const { handleToggleAdvanced } = useAdvanced({ const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
advanceState, advanceState,
emit, emit,
getProps, getProps,
@ -243,6 +244,7 @@
function setFormModel(key: string, value: any) { function setFormModel(key: string, value: any) {
formModel[key] = value formModel[key] = value
const { validateTrigger } = unref(getBindValue) const { validateTrigger } = unref(getBindValue)
if (!validateTrigger || validateTrigger === 'change') { if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {}) validateFields([key]).catch((_) => {})
@ -299,6 +301,7 @@
getFormActionBindProps: computed( getFormActionBindProps: computed(
(): Recordable => ({ ...getProps.value, ...advanceState }), (): Recordable => ({ ...getProps.value, ...advanceState }),
), ),
fieldsIsAdvancedMap,
...formActionType, ...formActionType,
} }
}, },

View File

@ -1,5 +1,5 @@
import type { Component } from 'vue' import type { Component } from 'vue';
import type { ComponentType } from './types/index' import type { ComponentType } from './types/index';
/** /**
* Component list, register here to setting it in the form * Component list, register here to setting it in the form
@ -19,65 +19,65 @@ import {
Slider, Slider,
Rate, Rate,
Divider, Divider,
} from 'ant-design-vue' } from 'ant-design-vue';
import ApiRadioGroup from './components/ApiRadioGroup.vue' import ApiRadioGroup from './components/ApiRadioGroup.vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue' import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue' import ApiSelect from './components/ApiSelect.vue';
import ApiTree from './components/ApiTree.vue' import ApiTree from './components/ApiTree.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue' import ApiTreeSelect from './components/ApiTreeSelect.vue';
import ApiCascader from './components/ApiCascader.vue' import ApiCascader from './components/ApiCascader.vue';
import ApiTransfer from './components/ApiTransfer.vue' import ApiTransfer from './components/ApiTransfer.vue';
import { BasicUpload } from '/@/components/Upload' import { BasicUpload } from '/@/components/Upload';
import { StrengthMeter } from '/@/components/StrengthMeter' import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon' import { IconPicker } from '/@/components/Icon';
import { CountdownInput } from '/@/components/CountDown' import { CountdownInput } from '/@/components/CountDown';
const componentMap = new Map<ComponentType, Component>() const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input) componentMap.set('Input', Input);
componentMap.set('InputGroup', Input.Group) componentMap.set('InputGroup', Input.Group);
componentMap.set('InputPassword', Input.Password) componentMap.set('InputPassword', Input.Password);
componentMap.set('InputSearch', Input.Search) componentMap.set('InputSearch', Input.Search);
componentMap.set('InputTextArea', Input.TextArea) componentMap.set('InputTextArea', Input.TextArea);
componentMap.set('InputNumber', InputNumber) componentMap.set('InputNumber', InputNumber);
componentMap.set('AutoComplete', AutoComplete) componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select) componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect) componentMap.set('ApiSelect', ApiSelect);
componentMap.set('ApiTree', ApiTree) componentMap.set('ApiTree', ApiTree);
componentMap.set('TreeSelect', TreeSelect) componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect) componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('ApiRadioGroup', ApiRadioGroup) componentMap.set('ApiRadioGroup', ApiRadioGroup);
componentMap.set('Switch', Switch) componentMap.set('Switch', Switch);
componentMap.set('RadioButtonGroup', RadioButtonGroup) componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioGroup', Radio.Group) componentMap.set('RadioGroup', Radio.Group);
componentMap.set('Checkbox', Checkbox) componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group) componentMap.set('CheckboxGroup', Checkbox.Group);
componentMap.set('ApiCascader', ApiCascader) componentMap.set('ApiCascader', ApiCascader);
componentMap.set('Cascader', Cascader) componentMap.set('Cascader', Cascader);
componentMap.set('Slider', Slider) componentMap.set('Slider', Slider);
componentMap.set('Rate', Rate) componentMap.set('Rate', Rate);
componentMap.set('ApiTransfer', ApiTransfer) componentMap.set('ApiTransfer', ApiTransfer);
componentMap.set('DatePicker', DatePicker) componentMap.set('DatePicker', DatePicker);
componentMap.set('MonthPicker', DatePicker.MonthPicker) componentMap.set('MonthPicker', DatePicker.MonthPicker);
componentMap.set('RangePicker', DatePicker.RangePicker) componentMap.set('RangePicker', DatePicker.RangePicker);
componentMap.set('WeekPicker', DatePicker.WeekPicker) componentMap.set('WeekPicker', DatePicker.WeekPicker);
componentMap.set('TimePicker', TimePicker) componentMap.set('TimePicker', TimePicker);
componentMap.set('StrengthMeter', StrengthMeter) componentMap.set('StrengthMeter', StrengthMeter);
componentMap.set('IconPicker', IconPicker) componentMap.set('IconPicker', IconPicker);
componentMap.set('InputCountDown', CountdownInput) componentMap.set('InputCountDown', CountdownInput);
componentMap.set('Upload', BasicUpload) componentMap.set('Upload', BasicUpload);
componentMap.set('Divider', Divider) componentMap.set('Divider', Divider);
export function add(compName: ComponentType, component: Component) { export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component) componentMap.set(compName, component);
} }
export function del(compName: ComponentType) { export function del(compName: ComponentType) {
componentMap.delete(compName) componentMap.delete(compName);
} }
export { componentMap } export { componentMap };

View File

@ -19,20 +19,20 @@
</a-cascader> </a-cascader>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue' import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue';
import { Cascader } from 'ant-design-vue' import { Cascader } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es';
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
interface Option { interface Option {
value: string value: string;
label: string label: string;
loading?: boolean loading?: boolean;
isLeaf?: boolean isLeaf?: boolean;
children?: Option[] children?: Option[];
} }
export default defineComponent({ export default defineComponent({
name: 'ApiCascader', name: 'ApiCascader',
@ -71,117 +71,117 @@
}, },
emits: ['change', 'defaultChange'], emits: ['change', 'defaultChange'],
setup(props, { emit }) { setup(props, { emit }) {
const apiData = ref<any[]>([]) const apiData = ref<any[]>([]);
const options = ref<Option[]>([]) const options = ref<Option[]>([]);
const loading = ref<boolean>(false) const loading = ref<boolean>(false);
const emitData = ref<any[]>([]) const emitData = ref<any[]>([]);
const isFirstLoad = ref(true) const isFirstLoad = ref(true);
const { t } = useI18n() const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData) const [state] = useRuleFormItem(props, 'value', 'change', emitData);
watch( watch(
apiData, apiData,
(data) => { (data) => {
const opts = generatorOptions(data) const opts = generatorOptions(data);
options.value = opts options.value = opts;
}, },
{ deep: true }, { deep: true },
) );
function generatorOptions(options: any[]): Option[] { function generatorOptions(options: any[]): Option[] {
const { labelField, valueField, numberToString, childrenField, isLeaf } = props const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
return options.reduce((prev, next: Recordable) => { return options.reduce((prev, next: Recordable) => {
if (next) { if (next) {
const value = next[valueField] const value = next[valueField];
const item = { const item = {
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false, isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
} };
const children = Reflect.get(next, childrenField) const children = Reflect.get(next, childrenField);
if (children) { if (children) {
Reflect.set(item, childrenField, generatorOptions(children)) Reflect.set(item, childrenField, generatorOptions(children));
} }
prev.push(item) prev.push(item);
} }
return prev return prev;
}, [] as Option[]) }, [] as Option[]);
} }
async function initialFetch() { async function initialFetch() {
const api = props.api const api = props.api;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
apiData.value = [] apiData.value = [];
loading.value = true loading.value = true;
try { try {
const res = await api(props.initFetchParams) const res = await api(props.initFetchParams);
if (Array.isArray(res)) { if (Array.isArray(res)) {
apiData.value = res apiData.value = res;
return return;
} }
if (props.resultField) { if (props.resultField) {
apiData.value = get(res, props.resultField) || [] apiData.value = get(res, props.resultField) || [];
} }
} catch (error) { } catch (error) {
console.warn(error) console.warn(error);
} finally { } finally {
loading.value = false loading.value = false;
} }
} }
async function loadData(selectedOptions: Option[]) { async function loadData(selectedOptions: Option[]) {
const targetOption = selectedOptions[selectedOptions.length - 1] const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true targetOption.loading = true;
const api = props.api const api = props.api;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
try { try {
const res = await api({ const res = await api({
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'), [props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
}) });
if (Array.isArray(res)) { if (Array.isArray(res)) {
const children = generatorOptions(res) const children = generatorOptions(res);
targetOption.children = children targetOption.children = children;
return return;
} }
if (props.resultField) { if (props.resultField) {
const children = generatorOptions(get(res, props.resultField) || []) const children = generatorOptions(get(res, props.resultField) || []);
targetOption.children = children targetOption.children = children;
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e);
} finally { } finally {
targetOption.loading = false targetOption.loading = false;
} }
} }
watchEffect(() => { watchEffect(() => {
props.immediate && initialFetch() props.immediate && initialFetch();
}) });
watch( watch(
() => props.initFetchParams, () => props.initFetchParams,
() => { () => {
!unref(isFirstLoad) && initialFetch() !unref(isFirstLoad) && initialFetch();
}, },
{ deep: true }, { deep: true },
) );
function handleChange(keys, args) { function handleChange(keys, args) {
emitData.value = keys emitData.value = keys;
emit('defaultChange', keys, args) emit('defaultChange', keys, args);
} }
function handleRenderDisplay({ labels, selectedOptions }) { function handleRenderDisplay({ labels, selectedOptions }) {
if (unref(emitData).length === selectedOptions.length) { if (unref(emitData).length === selectedOptions.length) {
return labels.join(' / ') return labels.join(' / ');
} }
if (props.displayRenderArray) { if (props.displayRenderArray) {
return props.displayRenderArray.join(' / ') return props.displayRenderArray.join(' / ');
} }
return '' return '';
} }
return { return {
@ -192,7 +192,7 @@
handleChange, handleChange,
loadData, loadData,
handleRenderDisplay, handleRenderDisplay,
} };
}, },
}) });
</script> </script>

View File

@ -59,7 +59,7 @@
immediate: propTypes.bool.def(true), immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false), alwaysLoad: propTypes.bool.def(false),
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change', 'update:value'],
setup(props, { emit }) { setup(props, { emit }) {
const options = ref<OptionsItem[]>([]) const options = ref<OptionsItem[]>([])
const loading = ref(false) const loading = ref(false)
@ -91,6 +91,13 @@
props.immediate && !props.alwaysLoad && fetch() props.immediate && !props.alwaysLoad && fetch()
}) })
watch(
() => state.value,
(v) => {
emit('update:value', v)
},
)
watch( watch(
() => props.params, () => props.params,
() => { () => {

View File

@ -12,13 +12,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue' import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue';
import { Transfer } from 'ant-design-vue' import { Transfer } from 'ant-design-vue';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer' import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
export default defineComponent({ export default defineComponent({
name: 'ApiTransfer', name: 'ApiTransfer',
components: { Transfer }, components: { Transfer },
@ -47,18 +47,18 @@
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {
const _dataSource = ref<TransferItem[]>([]) const _dataSource = ref<TransferItem[]>([]);
const _targetKeys = ref<string[]>([]) const _targetKeys = ref<string[]>([]);
const { t } = useI18n() const { t } = useI18n();
const getAttrs = computed(() => { const getAttrs = computed(() => {
return { return {
...(!props.api ? { dataSource: unref(_dataSource) } : {}), ...(!props.api ? { dataSource: unref(_dataSource) } : {}),
...attrs, ...attrs,
} };
}) });
const getdataSource = computed(() => { const getdataSource = computed(() => {
const { labelField, valueField } = props const { labelField, valueField } = props;
return unref(_dataSource).reduce((prev, next: Recordable) => { return unref(_dataSource).reduce((prev, next: Recordable) => {
if (next) { if (next) {
@ -66,69 +66,69 @@
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
title: next[labelField], title: next[labelField],
key: next[valueField], key: next[valueField],
}) });
} }
return prev return prev;
}, [] as TransferItem[]) }, [] as TransferItem[]);
}) });
const getTargetKeys = computed<string[]>(() => { const getTargetKeys = computed<string[]>(() => {
if (unref(_targetKeys).length > 0) { if (unref(_targetKeys).length > 0) {
return unref(_targetKeys) return unref(_targetKeys);
} }
if (Array.isArray(props.value)) { if (Array.isArray(props.value)) {
return props.value return props.value;
} }
return [] return [];
}) });
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) { function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
_targetKeys.value = keys _targetKeys.value = keys;
console.log(direction) console.log(direction);
console.log(moveKeys) console.log(moveKeys);
emit('change', keys) emit('change', keys);
} }
watchEffect(() => { watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch() props.immediate && !props.alwaysLoad && fetch();
}) });
watch( watch(
() => props.params, () => props.params,
() => { () => {
fetch() fetch();
}, },
{ deep: true }, { deep: true },
) );
async function fetch() { async function fetch() {
const api = props.api const api = props.api;
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
if (Array.isArray(props.dataSource)) { if (Array.isArray(props.dataSource)) {
_dataSource.value = props.dataSource _dataSource.value = props.dataSource;
} }
return return;
} }
_dataSource.value = [] _dataSource.value = [];
try { try {
const res = await api(props.params) const res = await api(props.params);
if (Array.isArray(res)) { if (Array.isArray(res)) {
_dataSource.value = res _dataSource.value = res;
emitChange() emitChange();
return return;
} }
if (props.resultField) { if (props.resultField) {
_dataSource.value = get(res, props.resultField) || [] _dataSource.value = get(res, props.resultField) || [];
} }
emitChange() emitChange();
} catch (error) { } catch (error) {
console.warn(error) console.warn(error);
} finally { } finally {
} }
} }
function emitChange() { 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> </script>

View File

@ -10,12 +10,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue' import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import { Tree } from 'ant-design-vue' import { Tree } from 'ant-design-vue';
import { isArray, isFunction } from '/@/utils/is' import { isArray, isFunction } from '/@/utils/is';
import { get } from 'lodash-es' import { get } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({ export default defineComponent({
name: 'ApiTree', name: 'ApiTree',
components: { ATree: Tree, LoadingOutlined }, components: { ATree: Tree, LoadingOutlined },
@ -28,63 +28,63 @@
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {
const treeData = ref<Recordable[]>([]) const treeData = ref<Recordable[]>([]);
const isFirstLoaded = ref<Boolean>(false) const isFirstLoaded = ref<Boolean>(false);
const loading = ref(false) const loading = ref(false);
const getAttrs = computed(() => { const getAttrs = computed(() => {
return { return {
...(props.api ? { treeData: unref(treeData) } : {}), ...(props.api ? { treeData: unref(treeData) } : {}),
...attrs, ...attrs,
} };
}) });
function handleChange(...args) { function handleChange(...args) {
emit('change', ...args) emit('change', ...args);
} }
watch( watch(
() => props.params, () => props.params,
() => { () => {
!unref(isFirstLoaded) && fetch() !unref(isFirstLoaded) && fetch();
}, },
{ deep: true }, { deep: true },
) );
watch( watch(
() => props.immediate, () => props.immediate,
(v) => { (v) => {
v && !isFirstLoaded.value && fetch() v && !isFirstLoaded.value && fetch();
}, },
) );
onMounted(() => { onMounted(() => {
props.immediate && fetch() props.immediate && fetch();
}) });
async function fetch() { async function fetch() {
const { api, afterFetch } = props const { api, afterFetch } = props;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
loading.value = true loading.value = true;
treeData.value = [] treeData.value = [];
let result let result;
try { try {
result = await api(props.params) result = await api(props.params);
} catch (e) { } catch (e) {
console.error(e) console.error(e);
} }
if (afterFetch && isFunction(afterFetch)) { if (afterFetch && isFunction(afterFetch)) {
result = afterFetch(result) result = afterFetch(result);
} }
loading.value = false loading.value = false;
if (!result) return if (!result) return;
if (!isArray(result)) { if (!isArray(result)) {
result = get(result, props.resultField) result = get(result, props.resultField);
} }
treeData.value = (result as Recordable[]) || [] treeData.value = (result as Recordable[]) || [];
isFirstLoaded.value = true isFirstLoaded.value = true;
emit('options-change', treeData.value) emit('options-change', treeData.value);
} }
return { getAttrs, loading, handleChange } return { getAttrs, loading, handleChange };
}, },
}) });
</script> </script>

View File

@ -44,6 +44,9 @@
formActionType: { formActionType: {
type: Object as PropType<FormActionType>, type: Object as PropType<FormActionType>,
}, },
isAdvanced: {
type: Boolean,
},
}, },
setup(props, { slots }) { setup(props, { slots }) {
const { t } = useI18n() const { t } = useI18n()
@ -103,8 +106,8 @@
const { show, ifShow } = props.schema const { show, ifShow } = props.schema
const { showAdvancedButton } = props.formProps const { showAdvancedButton } = props.formProps
const itemIsAdvanced = showAdvancedButton const itemIsAdvanced = showAdvancedButton
? isBoolean(props.schema.isAdvanced) ? isBoolean(props.isAdvanced)
? props.schema.isAdvanced ? props.isAdvanced
: true : true
: true : true
@ -136,7 +139,6 @@
dynamicRules, dynamicRules,
required, required,
} = props.schema } = props.schema
if (isFunction(dynamicRules)) { if (isFunction(dynamicRules)) {
return dynamicRules(unref(getValues)) as ValidationRule[] return dynamicRules(unref(getValues)) as ValidationRule[]
} }
@ -282,7 +284,6 @@
...on, ...on,
...bindValue, ...bindValue,
} }
if (!renderComponentContent) { if (!renderComponentContent) {
return <Comp {...compAttr} /> return <Comp {...compAttr} />
} }
@ -291,6 +292,7 @@
: { : {
default: () => renderComponentContent, default: () => renderComponentContent,
} }
return <Comp {...compAttr}>{compSlot}</Comp> return <Comp {...compAttr}>{compSlot}</Comp>
} }

View File

@ -1,20 +1,20 @@
import type { ValidationRule } from 'ant-design-vue/lib/form/Form' import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { ComponentType } from './types/index' import type { ComponentType } from './types/index';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil';
import { isNumber, isObject } from '/@/utils/is' import { isNumber, isObject } from '/@/utils/is';
const { t } = useI18n() const { t } = useI18n();
/** /**
* @description: placeholder * @description: placeholder
*/ */
export function createPlaceholderMessage(component: ComponentType) { export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input') || component.includes('Complete')) { if (component.includes('Input') || component.includes('Complete')) {
return t('common.inputText') return t('common.inputText');
} }
if (component.includes('Picker')) { if (component.includes('Picker')) {
return t('common.chooseText') return t('common.chooseText');
} }
if ( if (
component.includes('Select') || component.includes('Select') ||
@ -24,15 +24,15 @@ export function createPlaceholderMessage(component: ComponentType) {
component.includes('Switch') component.includes('Switch')
) { ) {
// return `请选择${label}`; // 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() { function genType() {
return [...DATE_TYPE, 'RangePicker'] return [...DATE_TYPE, 'RangePicker'];
} }
export function setComponentRuleType( export function setComponentRuleType(
@ -41,34 +41,34 @@ export function setComponentRuleType(
valueFormat: string, valueFormat: string,
) { ) {
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) { if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
rule.type = valueFormat ? 'string' : 'object' rule.type = valueFormat ? 'string' : 'object';
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) { } else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {
rule.type = 'array' rule.type = 'array';
} else if (['InputNumber'].includes(component)) { } else if (['InputNumber'].includes(component)) {
rule.type = 'number' rule.type = 'number';
} }
} }
export function processDateValue(attr: Recordable, component: string) { export function processDateValue(attr: Recordable, component: string) {
const { valueFormat, value } = attr const { valueFormat, value } = attr;
if (valueFormat) { if (valueFormat) {
attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value;
} else if (DATE_TYPE.includes(component) && value) { } else if (DATE_TYPE.includes(component) && value) {
attr.value = dateUtil(attr.value) attr.value = dateUtil(attr.value);
} }
} }
export function handleInputNumberValue(component?: ComponentType, val?: any) { export function handleInputNumberValue(component?: ComponentType, val?: any) {
if (!component) return val if (!component) return val;
if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) { 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'];

View File

@ -1,21 +1,21 @@
import type { ColEx } from '../types' import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks' import type { AdvanceState } from '../types/hooks';
import { ComputedRef, getCurrentInstance, Ref } from 'vue' import { ComputedRef, getCurrentInstance, Ref, shallowReactive } from 'vue';
import type { FormProps, FormSchema } from '../types/form' import type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue' import { computed, unref, watch } from 'vue';
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is' import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint' import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core';
const BASIC_COL_LEN = 24 const BASIC_COL_LEN = 24;
interface UseAdvancedContext { interface UseAdvancedContext {
advanceState: AdvanceState advanceState: AdvanceState;
emit: EmitType emit: EmitType;
getProps: ComputedRef<FormProps> getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable formModel: Recordable;
defaultValueRef: Ref<Recordable> defaultValueRef: Ref<Recordable>;
} }
export default function ({ export default function ({
@ -26,104 +26,106 @@ export default function ({
formModel, formModel,
defaultValueRef, defaultValueRef,
}: UseAdvancedContext) { }: UseAdvancedContext) {
const vm = getCurrentInstance() const vm = getCurrentInstance();
const { realWidthRef, screenEnum, screenRef } = useBreakpoint() const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
const getEmptySpan = computed((): number => { const getEmptySpan = computed((): number => {
if (!advanceState.isAdvanced) { if (!advanceState.isAdvanced) {
return 0 return 0;
} }
// For some special cases, you need to manually specify additional blank lines // 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)) { if (isNumber(emptySpan)) {
return emptySpan return emptySpan;
} }
if (isObject(emptySpan)) { if (isObject(emptySpan)) {
const { span = 0 } = emptySpan const { span = 0 } = emptySpan;
const screen = unref(screenRef) as string const screen = unref(screenRef) as string;
const screenSpan = (emptySpan as any)[screen.toLowerCase()] const screenSpan = (emptySpan as any)[screen.toLowerCase()];
return screenSpan || span || 0 return screenSpan || span || 0;
} }
return 0 return 0;
}) });
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30) const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30);
watch( watch(
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)], [() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
() => { () => {
const { showAdvancedButton } = unref(getProps) const { showAdvancedButton } = unref(getProps);
if (showAdvancedButton) { if (showAdvancedButton) {
debounceUpdateAdvanced() debounceUpdateAdvanced();
} }
}, },
{ immediate: true }, { immediate: true },
) );
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) { function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
const width = unref(realWidthRef) const width = unref(realWidthRef);
const mdWidth = const mdWidth =
parseInt(itemCol.md as string) || parseInt(itemCol.md as string) ||
parseInt(itemCol.xs as string) || parseInt(itemCol.xs as string) ||
parseInt(itemCol.sm as string) || parseInt(itemCol.sm as string) ||
(itemCol.span as number) || (itemCol.span as number) ||
BASIC_COL_LEN BASIC_COL_LEN;
const lgWidth = parseInt(itemCol.lg as string) || mdWidth const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
const xlWidth = parseInt(itemCol.xl as string) || lgWidth const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
if (width <= screenEnum.LG) { if (width <= screenEnum.LG) {
itemColSum += mdWidth itemColSum += mdWidth;
} else if (width < screenEnum.XL) { } else if (width < screenEnum.XL) {
itemColSum += lgWidth itemColSum += lgWidth;
} else if (width < screenEnum.XXL) { } else if (width < screenEnum.XXL) {
itemColSum += xlWidth itemColSum += xlWidth;
} else { } else {
itemColSum += xxlWidth itemColSum += xxlWidth;
} }
if (isLastAction) { if (isLastAction) {
advanceState.hideAdvanceBtn = false advanceState.hideAdvanceBtn = false;
if (itemColSum <= BASIC_COL_LEN * 2) { if (itemColSum <= BASIC_COL_LEN * 2) {
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed // When less than or equal to 2 lines, the collapse and expand buttons are not displayed
advanceState.hideAdvanceBtn = true advanceState.hideAdvanceBtn = true;
advanceState.isAdvanced = true advanceState.isAdvanced = true;
} else if ( } else if (
itemColSum > BASIC_COL_LEN * 2 && itemColSum > BASIC_COL_LEN * 2 &&
itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3) itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
) { ) {
advanceState.hideAdvanceBtn = false advanceState.hideAdvanceBtn = false;
// More than 3 lines collapsed by default // More than 3 lines collapsed by default
} else if (!advanceState.isLoad) { } else if (!advanceState.isLoad) {
advanceState.isLoad = true advanceState.isLoad = true;
advanceState.isAdvanced = !advanceState.isAdvanced advanceState.isAdvanced = !advanceState.isAdvanced;
} }
return { isAdvanced: advanceState.isAdvanced, itemColSum } return { isAdvanced: advanceState.isAdvanced, itemColSum };
} }
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) { if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) {
return { isAdvanced: advanceState.isAdvanced, itemColSum } return { isAdvanced: advanceState.isAdvanced, itemColSum };
} else { } else {
// The first line is always displayed // The first line is always displayed
return { isAdvanced: true, itemColSum } return { isAdvanced: true, itemColSum };
} }
} }
const fieldsIsAdvancedMap = shallowReactive({});
function updateAdvanced() { function updateAdvanced() {
let itemColSum = 0 let itemColSum = 0;
let realItemColSum = 0 let realItemColSum = 0;
const { baseColProps = {} } = unref(getProps) const { baseColProps = {} } = unref(getProps);
for (const schema of unref(getSchema)) { for (const schema of unref(getSchema)) {
const { show, colProps } = schema const { show, colProps } = schema;
let isShow = true let isShow = true;
if (isBoolean(show)) { if (isBoolean(show)) {
isShow = show isShow = show;
} }
if (isFunction(show)) { if (isFunction(show)) {
@ -135,36 +137,36 @@ export default function ({
...unref(defaultValueRef), ...unref(defaultValueRef),
...formModel, ...formModel,
}, },
}) });
} }
if (isShow && (colProps || baseColProps)) { if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced( const { itemColSum: sum, isAdvanced } = getAdvanced(
{ ...baseColProps, ...colProps }, { ...baseColProps, ...colProps },
itemColSum, itemColSum,
) );
itemColSum = sum || 0 itemColSum = sum || 0;
if (isAdvanced) { 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() { function handleToggleAdvanced() {
advanceState.isAdvanced = !advanceState.isAdvanced advanceState.isAdvanced = !advanceState.isAdvanced;
} }
return { handleToggleAdvanced } return { handleToggleAdvanced, fieldsIsAdvancedMap };
} }

View File

@ -1,96 +1,96 @@
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form' import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface' import type { NamePath } from 'ant-design-vue/lib/form/interface';
import type { DynamicProps } from '/#/utils' import type { DynamicProps } from '/#/utils';
import { ref, onUnmounted, unref, nextTick, watch } from 'vue' import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log' import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils' 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 { export function useForm(props?: Props): UseFormReturnType {
const formRef = ref<Nullable<FormActionType>>(null) const formRef = ref<Nullable<FormActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false) const loadedRef = ref<Nullable<boolean>>(false);
async function getForm() { async function getForm() {
const form = unref(formRef) const form = unref(formRef);
if (!form) { if (!form) {
error( error(
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!', 'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
) );
} }
await nextTick() await nextTick();
return form as FormActionType return form as FormActionType;
} }
function register(instance: FormActionType) { function register(instance: FormActionType) {
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
formRef.value = null formRef.value = null;
loadedRef.value = null loadedRef.value = null;
}) });
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
formRef.value = instance formRef.value = instance;
loadedRef.value = true loadedRef.value = true;
watch( watch(
() => props, () => props,
() => { () => {
props && instance.setProps(getDynamicProps(props)) props && instance.setProps(getDynamicProps(props));
}, },
{ {
immediate: true, immediate: true,
deep: true, deep: true,
}, },
) );
} }
const methods: FormActionType = { const methods: FormActionType = {
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => { scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
const form = await getForm() const form = await getForm();
form.scrollToField(name, options) form.scrollToField(name, options);
}, },
setProps: async (formProps: Partial<FormProps>) => { setProps: async (formProps: Partial<FormProps>) => {
const form = await getForm() const form = await getForm();
form.setProps(formProps) form.setProps(formProps);
}, },
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm() const form = await getForm();
form.updateSchema(data) form.updateSchema(data);
}, },
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm() const form = await getForm();
form.resetSchema(data) form.resetSchema(data);
}, },
clearValidate: async (name?: string | string[]) => { clearValidate: async (name?: string | string[]) => {
const form = await getForm() const form = await getForm();
form.clearValidate(name) form.clearValidate(name);
}, },
resetFields: async () => { resetFields: async () => {
getForm().then(async (form) => { getForm().then(async (form) => {
await form.resetFields() await form.resetFields();
}) });
}, },
removeSchemaByField: async (field: string | string[]) => { removeSchemaByField: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByField(field) unref(formRef)?.removeSchemaByField(field);
}, },
// TODO promisify // TODO promisify
getFieldsValue: <T>() => { getFieldsValue: <T>() => {
return unref(formRef)?.getFieldsValue() as T return unref(formRef)?.getFieldsValue() as T;
}, },
setFieldsValue: async <T>(values: T) => { setFieldsValue: async <T>(values: T) => {
const form = await getForm() const form = await getForm();
form.setFieldsValue<T>(values) form.setFieldsValue<T>(values);
}, },
appendSchemaByField: async ( appendSchemaByField: async (
@ -98,25 +98,25 @@ export function useForm(props?: Props): UseFormReturnType {
prefixField: string | undefined, prefixField: string | undefined,
first: boolean, first: boolean,
) => { ) => {
const form = await getForm() const form = await getForm();
form.appendSchemaByField(schema, prefixField, first) form.appendSchemaByField(schema, prefixField, first);
}, },
submit: async (): Promise<any> => { submit: async (): Promise<any> => {
const form = await getForm() const form = await getForm();
return form.submit() return form.submit();
}, },
validate: async (nameList?: NamePath[]): Promise<Recordable> => { validate: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm() const form = await getForm();
return form.validate(nameList) return form.validate(nameList);
}, },
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => { validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm() const form = await getForm();
return form.validateFields(nameList) return form.validateFields(nameList);
}, },
} };
return [register, methods] return [register, methods];
} }

View File

@ -1,23 +1,23 @@
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form' import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface' import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw, nextTick } from 'vue' import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is' import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
import { deepMerge } from '/@/utils' import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper' import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil';
import { cloneDeep, uniqBy } from 'lodash-es' import { cloneDeep, uniqBy } from 'lodash-es';
import { error } from '/@/utils/log' import { error } from '/@/utils/log';
interface UseFormActionContext { interface UseFormActionContext {
emit: EmitType emit: EmitType;
getProps: ComputedRef<FormProps> getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable formModel: Recordable;
defaultValueRef: Ref<Recordable> defaultValueRef: Ref<Recordable>;
formElRef: Ref<FormActionType> formElRef: Ref<FormActionType>;
schemaRef: Ref<FormSchema[]> schemaRef: Ref<FormSchema[]>;
handleFormValues: Fn handleFormValues: Fn;
} }
export function useFormEvents({ export function useFormEvents({
emit, emit,
@ -30,22 +30,22 @@ export function useFormEvents({
handleFormValues, handleFormValues,
}: UseFormActionContext) { }: UseFormActionContext) {
async function resetFields(): Promise<void> { async function resetFields(): Promise<void> {
const { resetFunc, submitOnReset } = unref(getProps) const { resetFunc, submitOnReset } = unref(getProps);
resetFunc && isFunction(resetFunc) && (await resetFunc()) resetFunc && isFunction(resetFunc) && (await resetFunc());
const formEl = unref(formElRef) const formEl = unref(formElRef);
if (!formEl) return if (!formEl) return;
Object.keys(formModel).forEach((key) => { Object.keys(formModel).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key) const schema = unref(getSchema).find((item) => item.field === key);
const isInput = schema?.component && defaultValueComponents.includes(schema.component) const isInput = schema?.component && defaultValueComponents.includes(schema.component);
const defaultValue = cloneDeep(defaultValueRef.value[key]) const defaultValue = cloneDeep(defaultValueRef.value[key]);
formModel[key] = isInput ? defaultValue || '' : defaultValue formModel[key] = isInput ? defaultValue || '' : defaultValue;
}) });
nextTick(() => clearValidate()) nextTick(() => clearValidate());
emit('reset', toRaw(formModel)) emit('reset', toRaw(formModel));
submitOnReset && handleSubmit() submitOnReset && handleSubmit();
} }
/** /**
@ -54,78 +54,78 @@ export function useFormEvents({
async function setFieldsValue(values: Recordable): Promise<void> { async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema) const fields = unref(getSchema)
.map((item) => item.field) .map((item) => item.field)
.filter(Boolean) .filter(Boolean);
// key 支持 a.b.c 的嵌套写法 // key 支持 a.b.c 的嵌套写法
const delimiter = '.' const delimiter = '.';
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0) const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
const validKeys: string[] = [] const validKeys: string[] = [];
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key) const schema = unref(getSchema).find((item) => item.field === key);
let value = values[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 // 0| '' is allow
if (hasKey && fields.includes(key)) { if (hasKey && fields.includes(key)) {
// time type // time type
if (itemIsDateType(key)) { if (itemIsDateType(key)) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
const arr: any[] = [] const arr: any[] = [];
for (const ele of value) { for (const ele of value) {
arr.push(ele ? dateUtil(ele) : null) arr.push(ele ? dateUtil(ele) : null);
} }
formModel[key] = arr formModel[key] = arr;
} else { } else {
const { componentProps } = schema || {} const { componentProps } = schema || {};
let _props = componentProps as any let _props = componentProps as any;
if (typeof componentProps === 'function') { 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 { } else {
formModel[key] = value formModel[key] = value;
} }
validKeys.push(key) validKeys.push(key);
} else { } else {
nestKeyArray.forEach((nestKey: string) => { nestKeyArray.forEach((nestKey: string) => {
try { try {
const value = eval('values' + delimiter + nestKey) const value = eval('values' + delimiter + nestKey);
if (isDef(value)) { if (isDef(value)) {
formModel[nestKey] = value formModel[nestKey] = value;
validKeys.push(nestKey) validKeys.push(nestKey);
} }
} catch (e) { } catch (e) {
// key not exist // key not exist
if (isDef(defaultValueRef.value[nestKey])) { 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 * @description: Delete based on field name
*/ */
async function removeSchemaByField(fields: string | string[]): Promise<void> { async function removeSchemaByField(fields: string | string[]): Promise<void> {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema)) const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
if (!fields) { if (!fields) {
return return;
} }
let fieldList: string[] = isString(fields) ? [fields] : fields let fieldList: string[] = isString(fields) ? [fields] : fields;
if (isString(fields)) { if (isString(fields)) {
fieldList = [fields] fieldList = [fields];
} }
for (const field of fieldList) { 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 { function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void {
if (isString(field)) { if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field) const index = schemaList.findIndex((schema) => schema.field === field);
if (index !== -1) { if (index !== -1) {
delete formModel[field] delete formModel[field];
schemaList.splice(index, 1) schemaList.splice(index, 1);
} }
} }
} }
@ -145,92 +145,92 @@ export function useFormEvents({
* @description: Insert after a certain field, if not insert the last * @description: Insert after a certain field, if not insert the last
*/ */
async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) { 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) { if (!prefixField || index === -1 || first) {
first ? schemaList.unshift(schema) : schemaList.push(schema) first ? schemaList.unshift(schema) : schemaList.push(schema);
schemaRef.value = schemaList schemaRef.value = schemaList;
_setDefaultValue(schema) _setDefaultValue(schema);
return return;
} }
if (index !== -1) { 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>[]) { async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [] let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema) updateData.push(data as FormSchema);
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data] updateData = [...data];
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
) );
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', '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>[]) { async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [] let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema) updateData.push(data as FormSchema);
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data] updateData = [...data];
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
) );
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', '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) => { updateData.forEach((item) => {
unref(getSchema).forEach((val) => { unref(getSchema).forEach((val) => {
if (val.field === item.field) { if (val.field === item.field) {
const newSchema = deepMerge(val, item) const newSchema = deepMerge(val, item);
schema.push(newSchema as FormSchema) schema.push(newSchema as FormSchema);
} else { } 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[]) { function _setDefaultValue(data: FormSchema | FormSchema[]) {
let schemas: FormSchema[] = [] let schemas: FormSchema[] = [];
if (isObject(data)) { if (isObject(data)) {
schemas.push(data as FormSchema) schemas.push(data as FormSchema);
} }
if (isArray(data)) { if (isArray(data)) {
schemas = [...data] schemas = [...data];
} }
const obj: Recordable = {} const obj: Recordable = {};
const currentFieldsValue = getFieldsValue() const currentFieldsValue = getFieldsValue();
schemas.forEach((item) => { schemas.forEach((item) => {
if ( if (
item.component != 'Divider' && item.component != 'Divider' &&
@ -239,16 +239,16 @@ export function useFormEvents({
!isNullOrUnDef(item.defaultValue) && !isNullOrUnDef(item.defaultValue) &&
!(item.field in currentFieldsValue) !(item.field in currentFieldsValue)
) { ) {
obj[item.field] = item.defaultValue obj[item.field] = item.defaultValue;
} }
}) });
setFieldsValue(obj) setFieldsValue(obj);
} }
function getFieldsValue(): Recordable { function getFieldsValue(): Recordable {
const formEl = unref(formElRef) const formEl = unref(formElRef);
if (!formEl) return {} if (!formEl) return {};
return handleFormValues(toRaw(unref(formModel))) return handleFormValues(toRaw(unref(formModel)));
} }
/** /**
@ -256,44 +256,44 @@ export function useFormEvents({
*/ */
function itemIsDateType(key: string) { function itemIsDateType(key: string) {
return unref(getSchema).some((item) => { 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) { async function validateFields(nameList?: NamePath[] | undefined) {
return unref(formElRef)?.validateFields(nameList) return unref(formElRef)?.validateFields(nameList);
} }
async function validate(nameList?: NamePath[] | undefined) { async function validate(nameList?: NamePath[] | undefined) {
return await unref(formElRef)?.validate(nameList) return await unref(formElRef)?.validate(nameList);
} }
async function clearValidate(name?: string | string[]) { async function clearValidate(name?: string | string[]) {
await unref(formElRef)?.clearValidate(name) await unref(formElRef)?.clearValidate(name);
} }
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) { async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
await unref(formElRef)?.scrollToField(name, options) await unref(formElRef)?.scrollToField(name, options);
} }
/** /**
* @description: Form submission * @description: Form submission
*/ */
async function handleSubmit(e?: Event): Promise<void> { async function handleSubmit(e?: Event): Promise<void> {
e && e.preventDefault() e && e.preventDefault();
const { submitFunc } = unref(getProps) const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) { if (submitFunc && isFunction(submitFunc)) {
await submitFunc() await submitFunc();
return return;
} }
const formEl = unref(formElRef) const formEl = unref(formElRef);
if (!formEl) return if (!formEl) return;
try { try {
const values = await validate() const values = await validate();
const res = handleFormValues(values) const res = handleFormValues(values);
emit('submit', res) emit('submit', res);
} catch (error: any) { } catch (error: any) {
throw new Error(error) throw new Error(error);
} }
} }
@ -310,5 +310,5 @@ export function useFormEvents({
resetFields, resetFields,
setFieldsValue, setFieldsValue,
scrollToField, scrollToField,
} };
} }

View File

@ -1,31 +1,31 @@
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is' import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil';
import { unref } from 'vue' import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue' import type { Ref, ComputedRef } from 'vue';
import type { FormProps, FormSchema } from '../types/form' import type { FormProps, FormSchema } from '../types/form';
import { cloneDeep, set } from 'lodash-es' import { cloneDeep, set } from 'lodash-es';
interface UseFormValuesContext { interface UseFormValuesContext {
defaultValueRef: Ref<any> defaultValueRef: Ref<any>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
getProps: ComputedRef<FormProps> getProps: ComputedRef<FormProps>;
formModel: Recordable formModel: Recordable;
} }
/** /**
* @desription deconstruct array-link key. This method will mutate the target. * @desription deconstruct array-link key. This method will mutate the target.
*/ */
function tryDeconstructArray(key: string, value: any, target: Recordable) { function tryDeconstructArray(key: string, value: any, target: Recordable) {
const pattern = /^\[(.+)\]$/ const pattern = /^\[(.+)\]$/;
if (pattern.test(key)) { if (pattern.test(key)) {
const match = key.match(pattern) const match = key.match(pattern);
if (match && match[1]) { if (match && match[1]) {
const keys = match[1].split(',') const keys = match[1].split(',');
value = Array.isArray(value) ? value : [value] value = Array.isArray(value) ? value : [value];
keys.forEach((k, index) => { keys.forEach((k, index) => {
set(target, k.trim(), value[index]) set(target, k.trim(), value[index]);
}) });
return true 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. * @desription deconstruct object-link key. This method will mutate the target.
*/ */
function tryDeconstructObject(key: string, value: any, target: Recordable) { function tryDeconstructObject(key: string, value: any, target: Recordable) {
const pattern = /^\{(.+)\}$/ const pattern = /^\{(.+)\}$/;
if (pattern.test(key)) { if (pattern.test(key)) {
const match = key.match(pattern) const match = key.match(pattern);
if (match && match[1]) { if (match && match[1]) {
const keys = match[1].split(',') const keys = match[1].split(',');
value = isObject(value) ? value : {} value = isObject(value) ? value : {};
keys.forEach((k) => { keys.forEach((k) => {
set(target, k.trim(), value[k.trim()]) set(target, k.trim(), value[k.trim()]);
}) });
return true return true;
} }
} }
} }
@ -57,75 +57,82 @@ export function useFormValues({
// Processing form values // Processing form values
function handleFormValues(values: Recordable) { function handleFormValues(values: Recordable) {
if (!isObject(values)) { if (!isObject(values)) {
return {} return {};
} }
const res: Recordable = {} const res: Recordable = {};
for (const item of Object.entries(values)) { for (const item of Object.entries(values)) {
let [, value] = item let [, value] = item;
const [key] = item const [key] = item;
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) { if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
continue continue;
} }
const transformDateFunc = unref(getProps).transformDateFunc const transformDateFunc = unref(getProps).transformDateFunc;
if (isObject(value)) { if (isObject(value)) {
value = transformDateFunc?.(value) value = transformDateFunc?.(value);
} }
if (isArray(value) && value[0]?.format && value[1]?.format) { if (isArray(value) && value[0]?.format && value[1]?.format) {
value = value.map((item) => transformDateFunc?.(item)) value = value.map((item) => transformDateFunc?.(item));
} }
// Remove spaces // Remove spaces
if (isString(value)) { if (isString(value)) {
value = value.trim() value = value.trim();
} }
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) { 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 * @description: Processing time interval parameters
*/ */
function handleRangeTimeValue(values: Recordable) { function handleRangeTimeValue(values: Recordable) {
const fieldMapToTime = unref(getProps).fieldMapToTime const fieldMapToTime = unref(getProps).fieldMapToTime;
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) { if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
return values return values;
} }
for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) { for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
if (!field || !startTimeKey || !endTimeKey || !values[field]) { if (!field || !startTimeKey || !endTimeKey) {
continue 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) const [startTimeFormat, endTimeFormat] = Array.isArray(format) ? format : [format, format];
values[endTimeKey] = dateUtil(endTime).format(format)
Reflect.deleteProperty(values, field) values[startTimeKey] = dateUtil(startTime).format(startTimeFormat);
values[endTimeKey] = dateUtil(endTime).format(endTimeFormat);
Reflect.deleteProperty(values, field);
} }
return values return values;
} }
function initDefault() { function initDefault() {
const schemas = unref(getSchema) const schemas = unref(getSchema);
const obj: Recordable = {} const obj: Recordable = {};
schemas.forEach((item) => { schemas.forEach((item) => {
const { defaultValue } = item const { defaultValue } = item;
if (!isNullOrUnDef(defaultValue)) { if (!isNullOrUnDef(defaultValue)) {
obj[item.field] = defaultValue obj[item.field] = defaultValue;
if (formModel[item.field] === undefined) { 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 };
} }

View File

@ -1,34 +1,34 @@
import type { Ref } from 'vue' import type { Ref } from 'vue';
import { computed, unref } from 'vue' import { computed, unref } from 'vue';
import type { FormProps, FormSchema } from '../types/form' import type { FormProps, FormSchema } from '../types/form';
import { isNumber } from '/@/utils/is' import { isNumber } from '/@/utils/is';
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) { export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
return computed(() => { return computed(() => {
const schemaItem = unref(schemaItemRef) const schemaItem = unref(schemaItemRef);
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {} const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
const { labelWidth, disabledLabelWidth } = schemaItem const { labelWidth, disabledLabelWidth } = schemaItem;
const { const {
labelWidth: globalLabelWidth, labelWidth: globalLabelWidth,
labelCol: globalLabelCol, labelCol: globalLabelCol,
wrapperCol: globWrapperCol, wrapperCol: globWrapperCol,
layout, layout,
} = unref(propsRef) } = unref(propsRef);
// If labelWidth is set globally, all items setting // If labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
labelCol.style = { labelCol.style = {
textAlign: 'left', textAlign: 'left',
} };
return { labelCol, wrapperCol } return { labelCol, wrapperCol };
} }
let width = labelWidth || globalLabelWidth let width = labelWidth || globalLabelWidth;
const col = { ...globalLabelCol, ...labelCol } const col = { ...globalLabelCol, ...labelCol };
const wrapCol = { ...globWrapperCol, ...wrapperCol } const wrapCol = { ...globWrapperCol, ...wrapperCol };
if (width) { if (width) {
width = isNumber(width) ? `${width}px` : width width = isNumber(width) ? `${width}px` : width;
} }
return { return {
@ -37,6 +37,6 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` }, style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
...wrapCol, ...wrapCol,
}, },
} };
}) });
} }

View File

@ -1,15 +1,15 @@
import type { FieldMapToTime, FormSchema } from './types/form' import type { FieldMapToTime, FormSchema } from './types/form';
import type { CSSProperties, PropType } from 'vue' import type { CSSProperties, PropType } from 'vue';
import type { ColEx } from './types' import type { ColEx } from './types';
import type { TableActionType } from '/@/components/Table' import type { TableActionType } from '/@/components/Table';
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes' import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import type { RowProps } from 'ant-design-vue/lib/grid/Row' import type { RowProps } from 'ant-design-vue/lib/grid/Row';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
export const basicProps = { export const basicProps = {
model: { model: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
default: {}, default: () => ({}),
}, },
// 标签宽度 固定宽度 // 标签宽度 固定宽度
labelWidth: { labelWidth: {
@ -54,7 +54,7 @@ export const basicProps = {
transformDateFunc: { transformDateFunc: {
type: Function as PropType<Fn>, type: Function as PropType<Fn>,
default: (date: any) => { 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), rulesMessageJoinLabel: propTypes.bool.def(true),
@ -100,4 +100,4 @@ export const basicProps = {
labelAlign: propTypes.string, labelAlign: propTypes.string,
rowProps: Object as PropType<RowProps>, rowProps: Object as PropType<RowProps>,
} };

View File

@ -1,223 +1,227 @@
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface' import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
import type { VNode } from 'vue' import type { VNode } from 'vue';
import type { ButtonProps as AntdButtonProps } from '/@/components/Button' import type { ButtonProps as AntdButtonProps } from '/@/components/Button';
import type { FormItem } from './formItem' import type { FormItem } from './formItem';
import type { ColEx, ComponentType } from './index' import type { ColEx, ComponentType } from './index';
import type { TableActionType } from '/@/components/Table/src/types/table' import type { TableActionType } from '/@/components/Table/src/types/table';
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue';
import type { RowProps } from 'ant-design-vue/lib/grid/Row' 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 & { export type Rule = RuleObject & {
trigger?: 'blur' | 'change' | ['change', 'blur'] trigger?: 'blur' | 'change' | ['change', 'blur'];
} };
export interface RenderCallbackParams { export interface RenderCallbackParams {
schema: FormSchema schema: FormSchema;
values: Recordable values: Recordable;
model: Recordable model: Recordable;
field: string field: string;
} }
export interface ButtonProps extends AntdButtonProps { export interface ButtonProps extends AntdButtonProps {
text?: string text?: string;
} }
export interface FormActionType { export interface FormActionType {
submit: () => Promise<void> submit: () => Promise<void>;
setFieldsValue: <T>(values: T) => Promise<void> setFieldsValue: <T>(values: T) => Promise<void>;
resetFields: () => Promise<void> resetFields: () => Promise<void>;
getFieldsValue: () => Recordable getFieldsValue: () => Recordable;
clearValidate: (name?: string | string[]) => Promise<void> clearValidate: (name?: string | string[]) => Promise<void>;
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void> updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void> resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
setProps: (formProps: Partial<FormProps>) => Promise<void> setProps: (formProps: Partial<FormProps>) => Promise<void>;
removeSchemaByField: (field: string | string[]) => Promise<void> removeSchemaByField: (field: string | string[]) => Promise<void>;
appendSchemaByField: ( appendSchemaByField: (
schema: FormSchema, schema: FormSchema,
prefixField: string | undefined, prefixField: string | undefined,
first?: boolean | undefined, first?: boolean | undefined,
) => Promise<void> ) => Promise<void>;
validateFields: (nameList?: NamePath[]) => Promise<any> validateFields: (nameList?: NamePath[]) => Promise<any>;
validate: (nameList?: NamePath[]) => Promise<any> validate: (nameList?: NamePath[]) => Promise<any>;
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void> 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 { export interface FormProps {
name?: string name?: string;
layout?: 'vertical' | 'inline' | 'horizontal' layout?: 'vertical' | 'inline' | 'horizontal';
// Form value // Form value
model?: Recordable model?: Recordable;
// The width of all items in the entire form // The width of all items in the entire form
labelWidth?: number | string labelWidth?: number | string;
// alignment // alignment
labelAlign?: 'left' | 'right' labelAlign?: 'left' | 'right';
// Row configuration for the entire form // Row configuration for the entire form
rowProps?: RowProps rowProps?: RowProps;
// Submit form on reset // Submit form on reset
submitOnReset?: boolean submitOnReset?: boolean;
// Submit form on form changing // Submit form on form changing
submitOnChange?: boolean submitOnChange?: boolean;
// Col configuration for the entire form // Col configuration for the entire form
labelCol?: Partial<ColEx> labelCol?: Partial<ColEx>;
// Col configuration for the entire form // Col configuration for the entire form
wrapperCol?: Partial<ColEx> wrapperCol?: Partial<ColEx>;
// General row style // General row style
baseRowStyle?: CSSProperties baseRowStyle?: CSSProperties;
// General col configuration // General col configuration
baseColProps?: Partial<ColEx> baseColProps?: Partial<ColEx>;
// Form configuration rules // Form configuration rules
schemas?: FormSchema[] schemas?: FormSchema[];
// Function values used to merge into dynamic control form items // Function values used to merge into dynamic control form items
mergeDynamicData?: Recordable mergeDynamicData?: Recordable;
// Compact mode for search forms // Compact mode for search forms
compact?: boolean compact?: boolean;
// Blank line span // Blank line span
emptySpan?: number | Partial<ColEx> emptySpan?: number | Partial<ColEx>;
// Internal component size of the form // Internal component size of the form
size?: 'default' | 'small' | 'large' size?: 'default' | 'small' | 'large';
// Whether to disable // Whether to disable
disabled?: boolean disabled?: boolean;
// Time interval fields are mapped into multiple // Time interval fields are mapped into multiple
fieldMapToTime?: FieldMapToTime fieldMapToTime?: FieldMapToTime;
// Placeholder is set automatically // Placeholder is set automatically
autoSetPlaceHolder?: boolean autoSetPlaceHolder?: boolean;
// Auto submit on press enter on input // Auto submit on press enter on input
autoSubmitOnEnter?: boolean autoSubmitOnEnter?: boolean;
// Check whether the information is added to the label // Check whether the information is added to the label
rulesMessageJoinLabel?: boolean rulesMessageJoinLabel?: boolean;
// Whether to show collapse and expand buttons // 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 // 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 // Automatically collapse over the specified number of rows
autoAdvancedLine?: number autoAdvancedLine?: number;
// Always show lines // Always show lines
alwaysShowLines?: number alwaysShowLines?: number;
// Whether to show the operation button // Whether to show the operation button
showActionButtonGroup?: boolean showActionButtonGroup?: boolean;
// Reset button configuration // Reset button configuration
resetButtonOptions?: Partial<ButtonProps> resetButtonOptions?: Partial<ButtonProps>;
// Confirm button configuration // Confirm button configuration
submitButtonOptions?: Partial<ButtonProps> submitButtonOptions?: Partial<ButtonProps>;
// Operation column configuration // Operation column configuration
actionColOptions?: Partial<ColEx> actionColOptions?: Partial<ColEx>;
// Show reset button // Show reset button
showResetButton?: boolean showResetButton?: boolean;
// Show confirmation button // Show confirmation button
showSubmitButton?: boolean showSubmitButton?: boolean;
resetFunc?: () => Promise<void> resetFunc?: () => Promise<void>;
submitFunc?: () => Promise<void> submitFunc?: () => Promise<void>;
transformDateFunc?: (date: any) => string transformDateFunc?: (date: any) => string;
colon?: boolean colon?: boolean;
} }
export interface FormSchema { export interface FormSchema {
// Field name // Field name
field: string field: string;
// Event name triggered by internal value change, default change // Event name triggered by internal value change, default change
changeEvent?: string changeEvent?: string;
// Variable name bound to v-model Default value // Variable name bound to v-model Default value
valueField?: string valueField?: string;
// Label name // Label name
label: string | VNode label: string | VNode;
// Auxiliary text // Auxiliary text
subLabel?: string subLabel?: string;
// Help text on the right side of the text // Help text on the right side of the text
helpMessage?: helpMessage?:
| string | string
| string[] | string[]
| ((renderCallbackParams: RenderCallbackParams) => string | string[]) | ((renderCallbackParams: RenderCallbackParams) => string | string[]);
// BaseHelp component props // BaseHelp component props
helpComponentProps?: Partial<HelpComponentProps> helpComponentProps?: Partial<HelpComponentProps>;
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid // Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
labelWidth?: string | number labelWidth?: string | number;
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself // Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
disabledLabelWidth?: boolean disabledLabelWidth?: boolean;
// render component // render component
component: ComponentType component: ComponentType;
// Component parameters // Component parameters
componentProps?: componentProps?:
| ((opt: { | ((opt: {
schema: FormSchema schema: FormSchema;
tableAction: TableActionType tableAction: TableActionType;
formActionType: FormActionType formActionType: FormActionType;
formModel: Recordable formModel: Recordable;
}) => Recordable) }) => Recordable)
| object | object;
// Required // 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 // Validation rules
rules?: Rule[] rules?: Rule[];
// Check whether the information is added to the label // Check whether the information is added to the label
rulesMessageJoinLabel?: boolean rulesMessageJoinLabel?: boolean;
// Reference formModelItem // Reference formModelItem
itemProps?: Partial<FormItem> itemProps?: Partial<FormItem>;
// col configuration outside formModelItem // col configuration outside formModelItem
colProps?: Partial<ColEx> colProps?: Partial<ColEx>;
// 默认值 // 默认值
defaultValue?: any defaultValue?: any;
isAdvanced?: boolean
// 是否自动处理与时间相关组件的默认值
isHandleDateDefaultValue?: boolean;
isAdvanced?: boolean;
// Matching details components // 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 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 // Rendering col content requires outer wrapper form-item
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
renderComponentContent?: renderComponentContent?:
| ((renderCallbackParams: RenderCallbackParams) => any) | ((renderCallbackParams: RenderCallbackParams) => any)
| VNode | VNode
| VNode[] | VNode[]
| string | string;
// Custom slot, in from-item // Custom slot, in from-item
slot?: string slot?: string;
// Custom slot, similar to renderColContent // 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 { export interface HelpComponentProps {
maxWidth: string maxWidth: string;
// Whether to display the serial number // Whether to display the serial number
showIndex: boolean showIndex: boolean;
// Text list // Text list
text: any text: any;
// colour // colour
color: string color: string;
// font size // font size
fontSize: string fontSize: string;
icon: string icon: string;
absolute: boolean absolute: boolean;
// Positioning // Positioning
position: any position: any;
} }

View File

@ -1,83 +1,83 @@
type ColSpanType = number | string type ColSpanType = number | string;
export interface ColEx { export interface ColEx {
style?: any style?: any;
/** /**
* raster number of cells to occupy, 0 corresponds to display: none * raster number of cells to occupy, 0 corresponds to display: none
* @default none (0) * @default none (0)
* @type ColSpanType * @type ColSpanType
*/ */
span?: ColSpanType span?: ColSpanType;
/** /**
* raster order, used in flex layout mode * raster order, used in flex layout mode
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
order?: ColSpanType order?: ColSpanType;
/** /**
* the layout fill of flex * the layout fill of flex
* @default none * @default none
* @type ColSpanType * @type ColSpanType
*/ */
flex?: ColSpanType flex?: ColSpanType;
/** /**
* the number of cells to offset Col from the left * the number of cells to offset Col from the left
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
offset?: ColSpanType offset?: ColSpanType;
/** /**
* the number of cells that raster is moved to the right * the number of cells that raster is moved to the right
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
push?: ColSpanType push?: ColSpanType;
/** /**
* the number of cells that raster is moved to the left * the number of cells that raster is moved to the left
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
pull?: ColSpanType pull?: ColSpanType;
/** /**
* <576px and also default setting, could be a span value or an object containing above props * <576px and also default setting, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @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 * 576px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @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 * 768px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @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 * 992px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @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 * 1200px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @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 * 1600px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
} }
export type ComponentType = export type ComponentType =
@ -114,4 +114,4 @@ export type ComponentType =
| 'Slider' | 'Slider'
| 'Rate' | 'Rate'
| 'Divider' | 'Divider'
| 'ApiTransfer' | 'ApiTransfer';

View File

@ -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 }

View File

@ -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>

View File

@ -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 }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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']

View File

@ -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 }
}

View File

@ -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();
});
}

View File

@ -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);
});
}

View File

@ -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]
}

View File

@ -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);
}

View File

@ -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,
}
}

View File

@ -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 }
}

View File

@ -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,
},
}
})
}

View File

@ -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>,
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -0,0 +1,6 @@
export interface AdvanceState {
isAdvanced: boolean;
hideAdvanceBtn: boolean;
isLoad: boolean;
actionSpan: number;
}

View File

@ -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'

View File

@ -7,5 +7,5 @@ export * from './src/types/table'
export * from './src/types/pagination' export * from './src/types/pagination'
export * from './src/types/tableAction' export * from './src/types/tableAction'
export { useTable } from './src/hooks/useTable' 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' export type { EditRecordRow } from './src/components/editable'

View File

@ -11,7 +11,7 @@ import {
Radio, Radio,
} from 'ant-design-vue' } from 'ant-design-vue'
import type { ComponentType } from './types/componentType' 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>() const componentMap = new Map<ComponentType, Component>()

View File

@ -1,7 +1,7 @@
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table' import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'
import type { PaginationProps } from '../types/pagination' import type { PaginationProps } from '../types/pagination'
import type { DynamicProps } from '/#/utils' import type { DynamicProps } from '/#/utils'
import type { FormActionType } from '/@/components/Form' import type { FormActionType } from '../../../Form1'
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue'
import { getDynamicProps } from '/@/utils' import { getDynamicProps } from '/@/utils'
import { ref, onUnmounted, unref, watch, toRaw } from 'vue' import { ref, onUnmounted, unref, watch, toRaw } from 'vue'

View File

@ -1,7 +1,7 @@
import type { ComputedRef, Slots } from 'vue'; import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, FetchParams } from '../types/table'; import type { BasicTableProps, FetchParams } from '../types/table';
import { unref, computed } from 'vue'; import { unref, computed } from 'vue';
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '../../../Form1';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
export function useTableForm( export function useTableForm(

View File

@ -9,7 +9,7 @@ import type {
TableRowSelection, TableRowSelection,
SizeType, SizeType,
} from './types/table' } 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 { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'

View File

@ -1,6 +1,6 @@
import type { VNodeChild } from 'vue' import type { VNodeChild } from 'vue'
import type { PaginationProps } from './pagination' 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 { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table' import type { ColumnProps } from 'ant-design-vue/lib/table'

View File

@ -48,11 +48,9 @@ export function useRuleFormItem<T extends Recordable>(
}, },
set(value) { set(value) {
if (isEqual(value, defaultState.value)) return if (isEqual(value, defaultState.value)) return
innerState.value = value as T[keyof T] innerState.value = value as T[keyof T]
nextTick(() => { emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []))
emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || [])) // nextTick(() => {})
})
}, },
}) })

View File

@ -7,7 +7,7 @@
width="500px" width="500px"
@ok="handleSubmit" @ok="handleSubmit"
> >
<BasicForm @register="registerForm"> </BasicForm> <BasicForm @register="registerForm" />
</BasicDrawer> </BasicDrawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -57,6 +57,40 @@
return object return object
} }
const handleSubmit = async () => { 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 { try {
const values = await validate() const values = await validate()
let params = {} let params = {}

View File

@ -2,7 +2,7 @@ import { BasicColumn, FormSchema } from '/@/components/Table'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { h } from 'vue' import { h } from 'vue'
import { Tag } from 'ant-design-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 { formatDataByObject } from '/@/utils/index'
// import { getGriculturalDeviceBasic, getaGriculturalDevicePoint } from '/@/api/sys/user' // import { getGriculturalDeviceBasic, getaGriculturalDevicePoint } from '/@/api/sys/user'

View File

@ -2,7 +2,7 @@
<div> <div>
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm" />
<List <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" :data-source="list"
> >
<template #renderItem="{ item }"> <template #renderItem="{ item }">
@ -80,7 +80,7 @@
return formatDataByObject(resData) return formatDataByObject(resData)
}, },
params: { params: {
device_type: 4, device_type: 1,
agricultural_basic: e, agricultural_basic: e,
}, },
labelField: 'label', labelField: 'label',

View File

@ -7,7 +7,7 @@
</div> </div>
</div> </div>
</template> </template>
<div class="">{{ item.base_name }} </div> <div class="">{{ item.base_name }}-{{ item.monitoring_point }} </div>
</Card> </Card>
</template> </template>

View File

@ -5,7 +5,7 @@ import { Tag } from 'ant-design-vue'
import { Switch } from 'ant-design-vue' import { Switch } from 'ant-design-vue'
import { useMessage } from '/@/hooks/web/useMessage' import { useMessage } from '/@/hooks/web/useMessage'
import { updateFriendinks } from '/@/api/sys/user' 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 { Tinymce } from '/@/components/Tinymce/index'
import { uploadApi } from '/@/api/upload' import { uploadApi } from '/@/api/upload'

View File

@ -98,7 +98,7 @@
const obj = formatChartData() const obj = formatChartData()
setOptions({ setOptions({
grid: { left: '2%', right: '4%', top: '10%', bottom: '2%', containLabel: true }, grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
legend: { legend: {
data: obj.legendData, data: obj.legendData,
top: '0%', top: '0%',

View File

@ -91,7 +91,7 @@
// const max = Math.max(...largestOfFour(Data.series)) ?? 100 // const max = Math.max(...largestOfFour(Data.series)) ?? 100
setOptions({ setOptions({
grid: { left: '4%', right: '4%', top: '10%', bottom: '2%', containLabel: true }, grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
legend: { legend: {
data: obj.legendData, data: obj.legendData,
top: '0%', top: '0%',

View File

@ -78,7 +78,7 @@
const chartsInit = () => { const chartsInit = () => {
const obj = formatChartData() const obj = formatChartData()
setOptions({ setOptions({
grid: { left: '2%', right: '2%', top: '10%', bottom: '2%', containLabel: true }, grid: { left: '2%', right: '20px', top: '10%', bottom: '2%', containLabel: true },
legend: { legend: {
data: obj.legendData, data: obj.legendData,
top: '0%', top: '0%',

View File

@ -225,7 +225,7 @@
} }
}) })
setOptions({ setOptions({
grid: { left: '2%', right: '2%', top: '30px', bottom: '2%', containLabel: true }, grid: { left: '2%', right: '20px', top: '30px', bottom: '2%', containLabel: true },
legend: { legend: {
data: data.map((e) => e.name), data: data.map((e) => e.name),
top: '0%', top: '0%',

View File

@ -221,7 +221,7 @@
}) })
setOptions({ setOptions({
grid: { left: '2%', right: '2%', top: '30px', bottom: '2%', containLabel: true }, grid: { left: '2%', right: '20px', top: '30px', bottom: '2%', containLabel: true },
legend: { legend: {
data: data.map((e) => e.name), data: data.map((e) => e.name),
top: '0%', top: '0%',

View File

@ -94,6 +94,7 @@
import { computed } from '@vue/reactivity' import { computed } from '@vue/reactivity'
// import Am from './components/Star' // import Am from './components/Star'
const initTime = dayjs().format('YYYY-MM-DD HH:mm:ss') const initTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
// const initTime = '2022-10-20 00:00'
localStorage.removeItem('warning_id') localStorage.removeItem('warning_id')
export default defineComponent({ export default defineComponent({
components: { components: {
@ -142,13 +143,13 @@
localStorage.setItem('warning_id', arr.join(',')) localStorage.setItem('warning_id', arr.join(','))
fliterData.forEach((e, index) => { 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({ notification.warning({
message: '报警', message: message.base_name,
duration: duration, duration: duration,
class: 'warning-class', class: 'warning-class',
style: { style: {
@ -159,7 +160,7 @@
if (document.body.clientWidth < 3000) return document.body if (document.body.clientWidth < 3000) return document.body
return document.body.querySelector(`.visualization—xx`) return document.body.querySelector(`.visualization—xx`)
}, },
description: message, description: message.point_name + '' + message.content,
}) })
} }
@ -234,6 +235,10 @@
.ant-notification-notice-message { .ant-notification-notice-message {
color: #fff; color: #fff;
} }
.anticon-close {
color: #fff;
}
} }
</style> </style>
<style scoped lang="less"> <style scoped lang="less">