push
This commit is contained in:
122
lib/component/form/form/child-form.vue
Normal file
122
lib/component/form/form/child-form.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<a-row class="ns-form-body" :justify="formLayout.justify">
|
||||
<a-col :span="24" class="ns-child-form-title" v-html="title" v-if="title" />
|
||||
|
||||
<template v-for="(schema, index) in getSchema" :key="schema.field">
|
||||
<ns-form-item
|
||||
:span="getComponentSpan(schema)"
|
||||
:schema="schema"
|
||||
:index="index"
|
||||
:formModel="formModel">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-form-item>
|
||||
</template>
|
||||
<!-- 占位解决三列中部为空的问题 -->
|
||||
<a-col :span="formLayout.span" class="ns-form-item ns-form-item-placeholder" />
|
||||
</a-row>
|
||||
<a-divider class="ns-child-form-divider" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, defineComponent, inject, ref, watch } from 'vue';
|
||||
import { useFormModel } from '/nerv-lib/component/form/form/use-form-model';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsChildForm',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
schemas: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => [],
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, slots }) {
|
||||
const formElRef = ref();
|
||||
// const addChildForm = inject('addChildForm');
|
||||
const formLayout = inject('formLayout');
|
||||
|
||||
// formElRef.value ? addChildForm(formElRef) : '';
|
||||
// addChildForm(formElRef);
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
}));
|
||||
|
||||
const getComponentSpan = (schema: FormSchema) => {
|
||||
if (schema.class?.includes('ns-form-item-full')) {
|
||||
return 24;
|
||||
}
|
||||
return formLayout?.span;
|
||||
};
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const { schemas } = props;
|
||||
const formSchemas = schemas.filter((schema) => schema.component);
|
||||
return formSchemas as FormSchema[];
|
||||
});
|
||||
const isInitDefaultValueRef = ref(false);
|
||||
const {
|
||||
handleFormModel,
|
||||
initFormModel,
|
||||
resetFormModel,
|
||||
setFormModel,
|
||||
unsetFormModel,
|
||||
getFormModel,
|
||||
} = useFormModel({
|
||||
schemas: props.schemas,
|
||||
formModel: props.formModel,
|
||||
formElRef,
|
||||
});
|
||||
watch(
|
||||
() => getSchema.value,
|
||||
() => {
|
||||
if (isInitDefaultValueRef.value) {
|
||||
return;
|
||||
}
|
||||
initFormModel();
|
||||
isInitDefaultValueRef.value = true;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
formElRef,
|
||||
getBindValue,
|
||||
formLayout,
|
||||
getSchema,
|
||||
getComponentSpan,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-child-form-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
padding: 32px 0 24px 0;
|
||||
}
|
||||
.ns-child-form-divider {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
.ns-form-item-placeholder {
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
||||
335
lib/component/form/form/form-item.vue
Normal file
335
lib/component/form/form/form-item.vue
Normal file
@@ -0,0 +1,335 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<a-col
|
||||
:span="span"
|
||||
class="ns-form-item"
|
||||
:class="getFormItemClass"
|
||||
v-if="getIfShow"
|
||||
v-show="getShow">
|
||||
<a-form-item v-if="getFormItemDisplay" v-bind="formItemProps">
|
||||
<template #[item]="data" v-for="item in Object.keys(getSlots)">
|
||||
<slot :name="schema.field + '_' + item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<component :is="schema.component" v-bind="formProps">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</component>
|
||||
<!-- <div class="ns-tips">-->
|
||||
<!-- <slot :name="`${schema.field}_tips`"></slot>-->
|
||||
<!-- </div>-->
|
||||
</a-form-item>
|
||||
<template v-else>
|
||||
<component :is="schema.component" v-bind="formProps">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</component>
|
||||
<!-- <div class="ns-tips">-->
|
||||
<!-- <slot :name="`${schema.field}_tips`"></slot>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
</a-col>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, defineComponent, inject, nextTick, provide, ref, unref } from 'vue';
|
||||
import {
|
||||
forEach,
|
||||
isArray,
|
||||
isBoolean,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
mapKeys,
|
||||
upperFirst,
|
||||
get,
|
||||
} from 'lodash-es';
|
||||
import { isInputType } from '/nerv-lib/component/form/form-util';
|
||||
import { useParams } from '/nerv-lib/use/use-params';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsFormItem',
|
||||
components: {},
|
||||
props: {
|
||||
span: Number,
|
||||
schema: {
|
||||
type: Object as PropType<FormSchema>,
|
||||
default: () => ({}),
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const components = inject('components', () => ({}))();
|
||||
const setFormModel = inject('setFormModel') as Function;
|
||||
const getFormModel = inject('getFormModel') as Function;
|
||||
const submit = inject('submit') as Function;
|
||||
const validateRef = ref({});
|
||||
const formLayout = inject('formLayout');
|
||||
|
||||
const { getParams } = useParams();
|
||||
|
||||
const getSlots = computed(() => {
|
||||
const { schema } = props;
|
||||
return mapKeys(slots, (_val, key) => {
|
||||
return schema.field ? key.replace(schema.field + '_', '') : key;
|
||||
});
|
||||
});
|
||||
|
||||
provide(
|
||||
'rules',
|
||||
computed(() => {
|
||||
return props.schema?.rules || [];
|
||||
}),
|
||||
);
|
||||
|
||||
const formItemProps = computed(() => {
|
||||
const {
|
||||
schema: { field, rules, label, component, autoLink, formItemProps, extra },
|
||||
} = props;
|
||||
const tableComponent = ['nsTable'];
|
||||
let nsClass = '';
|
||||
component && tableComponent.includes(component) && (nsClass = 'ns-form-item-validate-self');
|
||||
return {
|
||||
class: nsClass,
|
||||
name: field,
|
||||
rules: rules || [],
|
||||
label: label,
|
||||
autoLink: autoLink || true,
|
||||
extra: extra,
|
||||
...formItemProps,
|
||||
...validateRef.value,
|
||||
};
|
||||
});
|
||||
|
||||
const getFormItemDisplay = computed(() => {
|
||||
const { schema } = props;
|
||||
return schema.displayFormItem !== false;
|
||||
});
|
||||
|
||||
const getFormItemClass = computed(() => {
|
||||
const { schema } = props;
|
||||
return schema.class;
|
||||
});
|
||||
|
||||
const getDisabled = computed(() => {
|
||||
const { dynamicDisabled } = props.schema;
|
||||
if (dynamicDisabled) {
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
return { disabled: dynamicDisabled };
|
||||
}
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
return { disabled: dynamicDisabled(props.formModel) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const getIfShow = computed(() => {
|
||||
const { ifShow } = props.schema;
|
||||
let isIfShow = true;
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(props.formModel);
|
||||
}
|
||||
// 不显示时候删除属性
|
||||
if (!isIfShow) {
|
||||
setFormModel(props.schema.field, null);
|
||||
}
|
||||
|
||||
return isIfShow && props.show;
|
||||
});
|
||||
|
||||
const getShow = computed(() => {
|
||||
const { show } = props.schema;
|
||||
let isShow = true;
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show(props.formModel);
|
||||
}
|
||||
return isShow;
|
||||
});
|
||||
// console.log(props.schema?.field, getShow.value, props.schema);
|
||||
|
||||
const getDynamicParams = computed(() => {
|
||||
const { dynamicParams, defaultParams, componentProps = {} } = props.schema;
|
||||
const { params = {} } = componentProps;
|
||||
if (dynamicParams) {
|
||||
const data = getParams(props.formModel, dynamicParams, { ...params, ...defaultParams });
|
||||
// console.log('getParams', data);
|
||||
return data;
|
||||
} else {
|
||||
return { ...params, ...defaultParams };
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 检测requiredParams是否全部获得数据
|
||||
* @param params
|
||||
*/
|
||||
function checkRequiredParams(params: Recordable) {
|
||||
const { dynamicParams } = props.schema;
|
||||
let { requiredParams } = props.schema;
|
||||
if (requiredParams) {
|
||||
if (requiredParams === true) requiredParams = dynamicParams as any;
|
||||
if (isFunction(requiredParams)) {
|
||||
console.error(
|
||||
'Property dynamicParams of props cannot set to Function when using requiredParams',
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
if (isString(requiredParams)) {
|
||||
if (isUndefined(params[requiredParams])) return false;
|
||||
} else if (isArray(requiredParams)) {
|
||||
for (let i = 0, l = requiredParams.length; i < l; i++) {
|
||||
if (isUndefined(params[requiredParams[i]])) return false;
|
||||
}
|
||||
} else if (isObject(requiredParams)) {
|
||||
const keys = Object.keys(requiredParams);
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
if (isUndefined(params[keys[i]])) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const formProps = computed(() => {
|
||||
const { formModel } = props;
|
||||
const {
|
||||
component,
|
||||
field,
|
||||
dynamicParams,
|
||||
changeEvent = 'change',
|
||||
valueField,
|
||||
addModel = [],
|
||||
autoAddLink = false,
|
||||
autoSubmit = false,
|
||||
} = props.schema;
|
||||
const isCheck =
|
||||
component && ['NsSwitch', 'NsCheckbox', 'Switch', 'Checkbox'].includes(component);
|
||||
const eventKey = `on${upperFirst(changeEvent)}`;
|
||||
const attr: Recordable = {};
|
||||
if (isInputType(component)) {
|
||||
attr.allowClear = true;
|
||||
}
|
||||
|
||||
const propsData: Recordable = {
|
||||
field,
|
||||
dynamicParams,
|
||||
...attr,
|
||||
size: 'default',
|
||||
...unref(getComponentsProps),
|
||||
...unref(getDisabled),
|
||||
};
|
||||
|
||||
// 如果有值
|
||||
if (getDynamicParams.value) {
|
||||
if (props.schema?.requiredParams) {
|
||||
if (checkRequiredParams(getDynamicParams.value)) {
|
||||
propsData.params = getDynamicParams.value;
|
||||
propsData.checkRequiredParams = true;
|
||||
} else {
|
||||
propsData.checkRequiredParams = false;
|
||||
}
|
||||
} else {
|
||||
propsData.params = getDynamicParams.value;
|
||||
}
|
||||
}
|
||||
|
||||
const bindValue: Recordable = {
|
||||
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
|
||||
};
|
||||
const on = {
|
||||
[eventKey]: (...args: Nullable<Recordable>[]) => {
|
||||
const [e] = args;
|
||||
if (propsData[eventKey]) {
|
||||
(propsData[eventKey] as Function)(...args);
|
||||
}
|
||||
const target = e ? e.target : null;
|
||||
const value = target ? (isCheck ? target.checked : target.value) : e;
|
||||
|
||||
setFormModel(field, value);
|
||||
|
||||
if (isArray(addModel)) {
|
||||
addModel.forEach((item) => {
|
||||
setFormModel(item, args[1] && args[1][item]);
|
||||
});
|
||||
} else {
|
||||
forEach(addModel as any, (value, key) => {
|
||||
setFormModel(key, args[1] && get(args[1], value));
|
||||
});
|
||||
}
|
||||
|
||||
if (autoAddLink) {
|
||||
const fieldLink = getFormModel('fieldLink') || {};
|
||||
fieldLink[field] = args[1];
|
||||
setFormModel('fieldLink', fieldLink);
|
||||
}
|
||||
|
||||
autoSubmit && nextTick(submit as any);
|
||||
},
|
||||
onValidateChange: (text: Object | undefined) => {
|
||||
if (isUndefined(text)) text = {};
|
||||
validateRef.value = text;
|
||||
},
|
||||
};
|
||||
return {
|
||||
...propsData,
|
||||
...on,
|
||||
...bindValue,
|
||||
formModel,
|
||||
};
|
||||
});
|
||||
|
||||
const getComponentsProps = computed(() => {
|
||||
const { componentProps = {} } = unref(props).schema;
|
||||
return componentProps;
|
||||
});
|
||||
|
||||
return {
|
||||
formItemProps,
|
||||
formProps,
|
||||
getShow,
|
||||
getIfShow,
|
||||
components,
|
||||
getFormItemDisplay,
|
||||
getFormItemClass,
|
||||
getSlots,
|
||||
formLayout,
|
||||
};
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.components = this.components;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-tips {
|
||||
height: auto;
|
||||
line-height: 32px;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
37
lib/component/form/form/form.d.ts
vendored
Normal file
37
lib/component/form/form/form.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
import type { RuleObject } from 'ant-design-vue/es/form/interface';
|
||||
declare global {
|
||||
type Rule = RuleObject & {
|
||||
trigger?: 'blur' | 'change' | ['change', 'blur'];
|
||||
};
|
||||
|
||||
interface FormSchema {
|
||||
field: string;
|
||||
label?: string;
|
||||
changeEvent?: string; //表单更新事件名称
|
||||
valueField?: string;
|
||||
component?: string; // 不定义,则为隐藏发送参数
|
||||
rules?: Recordable[];
|
||||
defaultValue?: any;
|
||||
componentProps?: any;
|
||||
formItemProps?: any;
|
||||
viewOnly?: Boolean; // 是否只展示,不发送参数
|
||||
ifShow?: Boolean | Function; //dom隐藏
|
||||
show?: Boolean | Function; //css隐藏
|
||||
// disabled?: Boolean; //是否禁用 (已废弃、和动态有重复部分)
|
||||
dynamicDisabled?: Boolean | Function; //是否禁用
|
||||
fieldMap?: Array<string> | Boolean | Props; //把一个值分割成多个值
|
||||
keepField?: Boolean; //映射时是否保留原值
|
||||
defaultParams?: Object; // 默认接口参数
|
||||
dynamicParams?: String | Array<string> | Recordable | Function; //动态接口参数,合并defaultParams赋值给params
|
||||
requiredParams?: Boolean | String | Array<string> | Recordable;
|
||||
addModel?: Array<string> | Props; // 额外发送参数,例如select中
|
||||
autoAddLink?: Boolean; //把需要联动的值映射到 formModel.fieldLink.field
|
||||
displayFormItem?: Boolean; //是否显示formItem (label)
|
||||
autoLink?: true; //是否使用规则自动绑定
|
||||
class?: String; //添加额外样式
|
||||
autoSubmit: Boolean; //是否操作后提交表单
|
||||
format: Function;
|
||||
}
|
||||
}
|
||||
338
lib/component/form/form/form.vue
Normal file
338
lib/component/form/form/form.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
class="ns-form"
|
||||
:class="getFormClass.class"
|
||||
v-bind="getBindValue"
|
||||
ref="formElRef"
|
||||
:model="formModel">
|
||||
<a-row class="ns-form-body" :justify="getFormClass.justify" :gutter="getFormClass.gutter">
|
||||
<template v-for="(schema, index) in getSchema" :key="schema.field">
|
||||
<ns-form-item
|
||||
:show="expandRef || index < splitNumber"
|
||||
:span="getComponentSpan(schema)"
|
||||
:schema="schema"
|
||||
:index="index"
|
||||
:formModel="formModel">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-form-item>
|
||||
</template>
|
||||
<a-col
|
||||
v-if="
|
||||
showAction
|
||||
? expandRef && getSchema.length % splitNumber === 0
|
||||
: (getSchema.length + 1) % splitNumber === 0
|
||||
"
|
||||
:span="getFormClass.span" />
|
||||
<a-col v-if="showAction" :span="getFormClass.span" class="ns-operate">
|
||||
<a-button @click="reset">重置</a-button>
|
||||
<a-button type="primary" html-type="submit" :loading="loading">搜索</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
class="ns-operate-expand"
|
||||
@click="expandRef = !expandRef"
|
||||
v-if="getSchema.length > splitNumber && showExpand">
|
||||
<template v-if="expandRef">
|
||||
收起
|
||||
<UpOutlined />
|
||||
</template>
|
||||
<template v-else>
|
||||
展开
|
||||
<DownOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<!-- <template v-for="(schema, index) in getSchema" :key="schema.field">-->
|
||||
<!-- <ns-form-item :schema="schema" :index="index" :formModel="formModel">-->
|
||||
<!-- <template #[item]="data" v-for="item in Object.keys($slots)">-->
|
||||
<!-- <slot :name="item" v-bind="data || {}"></slot>-->
|
||||
<!-- </template>-->
|
||||
<!-- </ns-form-item>-->
|
||||
<!-- </template>-->
|
||||
<!-- <a-row-->
|
||||
<!-- v-if="showAction"-->
|
||||
<!-- style="text-align: right; margin-left: auto"-->
|
||||
<!-- class="ns-form-item ns-form-item-op">-->
|
||||
<!-- <a-col span="24" class="operate">-->
|
||||
<!-- <a-button @click="reset">重置</a-button>-->
|
||||
<!-- <a-button style="margin-left: 10px" type="primary" html-type="submit" :loading="loading"-->
|
||||
<!-- >搜索</a-button-->
|
||||
<!-- >-->
|
||||
<!-- </a-col>-->
|
||||
<!-- </a-row>-->
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!--suppress TypeScriptCheckImport -->
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, nextTick, provide, ref, toRefs, watch } from 'vue';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
|
||||
import NsFormItem from '/nerv-lib/component/form/form/form-item.vue';
|
||||
import { useFormModel } from '/nerv-lib/component/form/form/use-form-model';
|
||||
import { formConfig } from '/nerv-base/config/form.config';
|
||||
import { formProps } from '/nerv-lib/component/form/form/props';
|
||||
import { isBoolean, isFunction } from 'lodash-es';
|
||||
import FormValidator from 'async-validator';
|
||||
import { watchDebounced, useElementSize } from '@vueuse/core';
|
||||
export default defineComponent({
|
||||
name: 'NsForm',
|
||||
components: { NsFormItem, DownOutlined, UpOutlined },
|
||||
props: formProps,
|
||||
emits: ['finish', 'submit', 'reset'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const formElRef = ref<null | Recordable>(null);
|
||||
const validateResult = ref(false);
|
||||
const { schemas } = toRefs(props);
|
||||
const isInitDefaultValueRef = ref(false);
|
||||
const expandRef = ref(props.expand);
|
||||
const formModel = computed(() => {
|
||||
return props.model;
|
||||
});
|
||||
const childForms = ref<any[]>([]);
|
||||
|
||||
function addChildForm(form: any) {
|
||||
childForms.value.push(form);
|
||||
}
|
||||
let splitNumber = ref(3);
|
||||
const { width: formWidth } = useElementSize(formElRef);
|
||||
provide('addChildForm', addChildForm);
|
||||
|
||||
const getFormClass = computed(() => {
|
||||
if (props.formLayout === 'flexVertical') {
|
||||
return formConfig.formLayout.flexVertical;
|
||||
}
|
||||
if (props.formLayout === 'flex') {
|
||||
return formConfig.formLayout.flex;
|
||||
}
|
||||
if (props.formLayout === 'flexv2') {
|
||||
return formConfig.formLayout.flexv2;
|
||||
}
|
||||
return formConfig.formLayout.vertical;
|
||||
});
|
||||
const FULL_SPAN_LIST = ['NsChildForm'];
|
||||
|
||||
const getComponentSpan = (schema: FormSchema) => {
|
||||
if (FULL_SPAN_LIST.includes(schema.component || '')) {
|
||||
return 24;
|
||||
}
|
||||
if (schema.class?.includes('ns-form-item-full')) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
return getFormClass.value.span;
|
||||
};
|
||||
|
||||
provide('formLayout', getFormClass.value);
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...getFormClass.value,
|
||||
...attrs,
|
||||
...props,
|
||||
title: '',
|
||||
onSubmit: submit,
|
||||
onFinish: finish,
|
||||
}));
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const { schemas } = props;
|
||||
const formSchemas = schemas.filter((schema) => schema.component);
|
||||
return formSchemas as FormSchema[];
|
||||
});
|
||||
|
||||
watchDebounced(
|
||||
() => formModel.value,
|
||||
() => {
|
||||
const rules = {};
|
||||
setRules(rules, getSchema.value);
|
||||
const validator = new FormValidator(rules);
|
||||
validator
|
||||
.validate(formModel.value)
|
||||
.then(() => {
|
||||
validateResult.value = true;
|
||||
})
|
||||
.catch(() => {
|
||||
validateResult.value = false;
|
||||
});
|
||||
},
|
||||
{
|
||||
debounce: 100,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
watchDebounced(
|
||||
() => formWidth.value,
|
||||
(val) => {
|
||||
if (val <= 768 && getFormClass.value['sm']) {
|
||||
getFormClass.value.span = getFormClass.value['sm'];
|
||||
splitNumber.value = 2;
|
||||
}
|
||||
if (val > 768 && getFormClass.value['lg']) {
|
||||
getFormClass.value.span = getFormClass.value['lg'];
|
||||
splitNumber.value = 3;
|
||||
}
|
||||
},
|
||||
{
|
||||
debounce: 100,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
function setRules(rules: Recordable, schemas: any[]) {
|
||||
schemas.forEach((schema) => {
|
||||
if (schema.rules) {
|
||||
const { ifShow } = schema;
|
||||
let isIfShow = true;
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(formModel.value);
|
||||
}
|
||||
|
||||
// console.log('ifShow', isIfShow);
|
||||
if (isIfShow) {
|
||||
rules[schema.field] = schema.rules;
|
||||
}
|
||||
}
|
||||
if (schema.componentProps?.schemas) {
|
||||
setRules(rules, schema.componentProps.schemas);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
handleFormModel,
|
||||
initFormModel,
|
||||
resetFormModel,
|
||||
setFormModel,
|
||||
unsetFormModel,
|
||||
getFormModel,
|
||||
} = useFormModel({
|
||||
schemas,
|
||||
formModel,
|
||||
formElRef,
|
||||
emit,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => getSchema.value,
|
||||
() => {
|
||||
if (isInitDefaultValueRef.value) {
|
||||
return;
|
||||
}
|
||||
initFormModel();
|
||||
isInitDefaultValueRef.value = true;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 数据验证成功后回调
|
||||
*/
|
||||
const submit = () => {
|
||||
emit('submit', formModel.value);
|
||||
return new Promise((resolve) => {
|
||||
resolve(formModel.value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单且数据验证成功后回调事件
|
||||
*/
|
||||
function finish() {
|
||||
const data = handleFormModel();
|
||||
emit('finish', data);
|
||||
return new Promise((resolve) => {
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动执行提交
|
||||
* @param val
|
||||
*/
|
||||
function triggerSubmit(val?: string[]) {
|
||||
return submit().then(() => {
|
||||
return nextTick().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
(formElRef as Recordable).value
|
||||
.validate(val)
|
||||
.then((_) => {
|
||||
resolve(finish());
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const reset = () => {
|
||||
resetFormModel();
|
||||
};
|
||||
|
||||
provide('setFormModel', setFormModel);
|
||||
provide('unsetFormModel', unsetFormModel);
|
||||
provide('getFormModel', getFormModel);
|
||||
provide('submit', triggerSubmit);
|
||||
|
||||
return {
|
||||
expandRef,
|
||||
formElRef,
|
||||
getBindValue,
|
||||
getSchema,
|
||||
formModel,
|
||||
setFormModel,
|
||||
getFormClass,
|
||||
validateResult,
|
||||
reset,
|
||||
triggerSubmit,
|
||||
getComponentSpan,
|
||||
splitNumber,
|
||||
finish,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-form {
|
||||
.ant-row {
|
||||
flex: 1;
|
||||
}
|
||||
.ns-operate {
|
||||
margin-bottom: 16px;
|
||||
text-align: right;
|
||||
margin-left: auto;
|
||||
|
||||
.ns-operate-expand {
|
||||
display: inline-block;
|
||||
padding: 4px 2px;
|
||||
border: unset !important;
|
||||
.anticon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
&:hover {
|
||||
border: unset !important;
|
||||
}
|
||||
&:focus {
|
||||
border: unset !important;
|
||||
}
|
||||
}
|
||||
.ant-btn {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
lib/component/form/form/index.ts
Normal file
11
lib/component/form/form/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import formItem from './form-item.vue';
|
||||
import childForm from './child-form.vue';
|
||||
import form from './form.vue';
|
||||
import type { App } from 'vue';
|
||||
|
||||
export const NsForm = function (app: App) {
|
||||
app.component(form.name, form);
|
||||
app.component(formItem.name, formItem);
|
||||
app.component(childForm.name, childForm);
|
||||
return app;
|
||||
};
|
||||
20
lib/component/form/form/props.ts
Normal file
20
lib/component/form/form/props.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
import { reactive, PropType } from 'vue';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
|
||||
export const formProps = {
|
||||
params: PropTypes.object.def(() => ({})),
|
||||
showAction: PropTypes.bool.def(false),
|
||||
// validateState: PropTypes.bool.def(false),
|
||||
schemas: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
model: PropTypes.object.def(() => reactive({})),
|
||||
loading: PropTypes.bool.def(false),
|
||||
formLayout: PropTypes.string.def('flexVertical'),
|
||||
expand: PropTypes.bool.def(true),
|
||||
showExpand: PropTypes.bool.def(false),
|
||||
};
|
||||
215
lib/component/form/form/use-form-model.ts
Normal file
215
lib/component/form/form/use-form-model.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
|
||||
|
||||
import type { UnwrapRef, Ref } from 'vue';
|
||||
import { unref, nextTick, toRaw } from 'vue';
|
||||
import { get, isArray, isNil, isUndefined, isPlainObject, forEach, isFunction } from 'lodash-es';
|
||||
import { isDateType, transformDate } from '/nerv-lib/component/form/form-util';
|
||||
|
||||
interface useFormModelType {
|
||||
schemas: Ref<UnwrapRef<FormSchema[]>>;
|
||||
formModel: Ref<Recordable>;
|
||||
formElRef: any;
|
||||
emit: Function;
|
||||
}
|
||||
|
||||
export function useFormModel({ schemas, formModel, formElRef, emit }: useFormModelType) {
|
||||
/**
|
||||
* 序列化传参 去除viewOnly,单个值转多个(时间范围、经纬度)
|
||||
* @param formSchema
|
||||
*/
|
||||
function handleFormModel(formSchema = schemas) {
|
||||
// console.log('formSchema', formSchema.value, formModel.value);
|
||||
return unref(formSchema).reduce((prev: Recordable, cur: FormSchema) => {
|
||||
const { field, fieldMap, keepField, format, addModel = [] } = cur;
|
||||
if (cur.viewOnly !== true) {
|
||||
let value = getFormModel(field);
|
||||
if (isFunction(format)) {
|
||||
value = format(value);
|
||||
}
|
||||
if (cur?.componentProps?.schemas) {
|
||||
Object.assign(prev, handleFormModel(cur.componentProps.schemas));
|
||||
} else if (fieldMap && !isNil(value)) {
|
||||
if (fieldMap === true) {
|
||||
Object.keys(value).forEach((key: string) => {
|
||||
Object.assign(prev, { [key]: value[key] });
|
||||
});
|
||||
} else if (isArray(fieldMap)) {
|
||||
if (!isArray(value) && ![null, undefined, ''].includes(value)) {
|
||||
console.error(`Field value isn't Array`);
|
||||
} else {
|
||||
fieldMap.forEach((item: string, index) => {
|
||||
Object.assign(prev, { [item]: value[index] });
|
||||
});
|
||||
}
|
||||
} else if (isPlainObject(fieldMap)) {
|
||||
if (!isPlainObject(value) && ![null, undefined, ''].includes(value)) {
|
||||
console.error(`Field value isn't Object`);
|
||||
} else {
|
||||
Object.keys(fieldMap).forEach((key: string) => {
|
||||
Object.assign(prev, { [key]: get(value, (<Props>fieldMap)[key]) });
|
||||
});
|
||||
}
|
||||
}
|
||||
//保留原值
|
||||
if (keepField === true) {
|
||||
Object.assign(prev, { [field]: value });
|
||||
}
|
||||
} else {
|
||||
Object.assign(prev, { [field]: value });
|
||||
if (isArray(addModel)) {
|
||||
addModel.forEach((item: string) => {
|
||||
Object.assign(prev, { [item]: unref(formModel)[item] });
|
||||
});
|
||||
} else {
|
||||
// @ts-ignore
|
||||
forEach(addModel as any, (value, key) => {
|
||||
Object.assign(prev, { [key]: unref(formModel)[key] });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认值, formModel > defaultValue
|
||||
*/
|
||||
function initFormModel() {
|
||||
unref(schemas).forEach((schema: FormSchema) => {
|
||||
const { field, component, defaultValue, componentProps } = schema;
|
||||
|
||||
if (schema.component) {
|
||||
if (!isUndefined(defaultValue) && isUndefined(getFormModel(field))) {
|
||||
if (defaultValue && isDateType(component)) {
|
||||
setFormModel(
|
||||
field,
|
||||
transformDate(component as string, defaultValue, componentProps.valueFormat),
|
||||
);
|
||||
} else {
|
||||
setFormModel(field, defaultValue);
|
||||
}
|
||||
} else if (isDateType(component) && !isUndefined(getFormModel(field))) {
|
||||
setFormModel(
|
||||
field,
|
||||
transformDate(component as string, getFormModel(field), componentProps.valueFormat),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setFormModel(field, defaultValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复默认值 defaultValue
|
||||
*/
|
||||
function resetFormModel() {
|
||||
Object.keys(formModel.value).forEach((key) => {
|
||||
unsetFormModel(key);
|
||||
});
|
||||
console.log(formModel.value);
|
||||
unref(schemas).forEach((schema: FormSchema) => {
|
||||
const { field, component, defaultValue, componentProps } = schema;
|
||||
setFormModel(field, defaultValue);
|
||||
|
||||
if (schema.component) {
|
||||
if (defaultValue && isDateType(component)) {
|
||||
setFormModel(
|
||||
field,
|
||||
transformDate(component as string, defaultValue, componentProps.valueFormat),
|
||||
);
|
||||
} else {
|
||||
setFormModel(field, defaultValue);
|
||||
}
|
||||
} else {
|
||||
setFormModel(field, defaultValue);
|
||||
}
|
||||
});
|
||||
|
||||
emit('reset', toRaw(formModel.value));
|
||||
}
|
||||
|
||||
async function validateFields(nameList?: string[] | undefined) {
|
||||
await nextTick();
|
||||
return unref(formElRef)?.validateFields(nameList);
|
||||
}
|
||||
|
||||
// function fieldRules(field: string) {
|
||||
// if (field === 'fieldLink') return undefined;
|
||||
// console.log('field', field, getNamePath.value);
|
||||
// return getNamePath.value[field]?.rules || undefined;
|
||||
// }
|
||||
|
||||
// function setNamePath(schemas: FormSchema[], namePathList: Recordable = {}) {
|
||||
// unref(schemas).forEach((schema: FormSchema) => {
|
||||
// if (schema.rules) {
|
||||
// namePathList[schema.field] = {
|
||||
// state: false,
|
||||
// rules: schema.rules,
|
||||
// };
|
||||
// }
|
||||
// if (schema?.componentProps?.schemas) {
|
||||
// setNamePath(schema?.componentProps?.schemas, namePathList);
|
||||
// }
|
||||
// });
|
||||
// console.log('namePathList', namePathList);
|
||||
// return namePathList;
|
||||
// }
|
||||
|
||||
// const getNamePath = computed(() => {
|
||||
// return setNamePath(unref(schemas));
|
||||
// });
|
||||
|
||||
/**
|
||||
* 设置表单值
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
function setFormModel(key: string, value?: any) {
|
||||
if (value === null) {
|
||||
delete unref(formModel)[key];
|
||||
} else {
|
||||
//select allowClear时值被设为undefined
|
||||
unref(formModel)[key] = value;
|
||||
}
|
||||
validateFields([key]).catch(() => {});
|
||||
// console.log('formModel', key, value, formModel.value);
|
||||
// if (fieldRules(key)) {
|
||||
// console.log('getNamePath', getNamePath.value);
|
||||
// validateFields([key])
|
||||
// .then((data) => {
|
||||
// console.log('validateFields then', key, data);
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// console.log('validateFields catch', key, e);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除表单值
|
||||
* @param key
|
||||
*/
|
||||
function unsetFormModel(key: string) {
|
||||
console.log('unsetFormModel', key);
|
||||
delete unref(formModel)[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得表单值
|
||||
* @param key
|
||||
*/
|
||||
function getFormModel(key: string) {
|
||||
return unref(formModel)[key];
|
||||
}
|
||||
|
||||
return {
|
||||
handleFormModel,
|
||||
initFormModel,
|
||||
resetFormModel,
|
||||
setFormModel,
|
||||
unsetFormModel,
|
||||
getFormModel,
|
||||
};
|
||||
}
|
||||
1
lib/component/form/form/use-form.ts
Normal file
1
lib/component/form/form/use-form.ts
Normal file
@@ -0,0 +1 @@
|
||||
export function useForm() {}
|
||||
Reference in New Issue
Block a user