|
|
|
<!-- @format -->
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<a-form
|
|
|
|
class="ns-form"
|
|
|
|
:class="getFormClass.class"
|
|
|
|
v-bind="getBindValue"
|
|
|
|
ref="formElRef"
|
|
|
|
:model="formModel">
|
|
|
|
<div v-if="showAction && showExpandAll" class="ns-form-title ns-title-extra-box">
|
|
|
|
<span>查询</span>
|
|
|
|
<a-button type="link" class="ns-operate-expand" @click="expandAll = !expandAll">
|
|
|
|
<template v-if="expandAll">
|
|
|
|
收起筛选
|
|
|
|
<UpOutlined />
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
展开筛选
|
|
|
|
<DownOutlined />
|
|
|
|
</template>
|
|
|
|
</a-button>
|
|
|
|
</div>
|
|
|
|
<a-row
|
|
|
|
v-show="expandAll"
|
|
|
|
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 expandAll = ref(props.expandAll);
|
|
|
|
const formModel = computed(() => {
|
|
|
|
return props.model;
|
|
|
|
});
|
|
|
|
const childForms = ref<any[]>([]);
|
|
|
|
|
|
|
|
function addChildForm(form: any) {
|
|
|
|
childForms.value.push(form);
|
|
|
|
}
|
|
|
|
let splitNumber = ref(4);
|
|
|
|
const { width: formWidth } = useElementSize(formElRef);
|
|
|
|
provide('addChildForm', addChildForm);
|
|
|
|
|
|
|
|
const getFormClass = computed(() => {
|
|
|
|
if (props.formLayout) {
|
|
|
|
return formConfig.formLayout[props.formLayout as keyof typeof formConfig.formLayout];
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
expandAll,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
|
|
@gap: 16px;
|
|
|
|
.ns-form {
|
|
|
|
.ant-row {
|
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
.ns-operate {
|
|
|
|
margin-bottom: @gap;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.ns-form-title {
|
|
|
|
text-align: left;
|
|
|
|
height: 22px;
|
|
|
|
// line-height: 32px;
|
|
|
|
//font-size: 16px;
|
|
|
|
font-weight: bold;
|
|
|
|
user-select: text;
|
|
|
|
margin-bottom: calc(@gap - 0px);
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
:deep(.ant-btn) {
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|