web/src/components/AnForm/components/FormModal.tsx

270 lines
6.5 KiB
TypeScript

import { Button, ButtonInstance, FormInstance, Message, Modal } from '@arco-design/web-vue';
import { useVModel } from '@vueuse/core';
import { InjectionKey, PropType, Ref } from 'vue';
import { getModel, setModel } from '../utils/useFormModel';
import { AnForm, AnFormInstance, AnFormSubmit } from './Form';
import { AnFormItemProps } from './FormItem';
import { cloneDeep } from 'lodash-es';
export interface AnFormModalContext {
visible: Ref<boolean>;
loading: Ref<boolean>;
anFormRef: Ref<AnFormInstance | null>;
submitForm: () => any | Promise<any>;
open: (data: Recordable) => void;
close: () => void;
modalTitle: () => any;
modalTrigger: () => any;
onClose: () => void;
}
export const AnFormModalContextKey = Symbol('AnFormModalContextKey') as InjectionKey<AnFormModalContext>;
/**
* 表单组件
*/
export const AnFormModal = defineComponent({
name: 'AnFormModal',
props: {
/**
* 弹窗标题
* @default
* ```ts
* '新增'
* ```
*/
title: {
type: [String, Function] as PropType<AnFormModalTitle>,
default: '新增',
},
/**
* 触发元素
* @default
* ```ts
* '新增'
* ```
*/
trigger: {
type: [Boolean, String, Function, Object] as PropType<AnFormModalTrigger>,
default: true,
},
/**
* 传递给Modal的props
* @example
* ```ts
* {
* closable: true
* }
* ```
*/
modalProps: {
type: Object as PropType<Omit<InstanceType<typeof Modal>['$props'], 'visible' | 'title'>>,
},
/**
* 表单数据
* @example
* ```ts
* {
* id: undefined
* }
* ```
*/
model: {
type: Object as PropType<Recordable>,
required: true,
},
/**
* 表单项
* @example
* ```ts
* [{
* field: 'name',
* label: '昵称',
* setter: 'input'
* }]
* ```
*/
items: {
type: Array as PropType<AnFormItemProps[]>,
default: () => [],
},
/**
* 提交表单
* @example
* ```ts
* (model) => api.user.addUser(model)
* ```
*/
submit: {
type: [Object, Function] as PropType<AnFormSubmit>,
},
/**
* 传给Form组件的参数
* @example
* ```ts
* {
* layout: 'vertical'
* }
* ```
*/
formProps: {
type: Object as PropType<Omit<FormInstance['$props'], 'model' | 'ref'>>,
},
},
emits: ['update:model', 'submited'],
setup(props, { emit }) {
const model = useVModel(props, 'model', emit);
const originModel = cloneDeep(model.value);
const anFormRef = ref<AnFormInstance | null>(null);
const visible = ref(false);
const loading = ref(false);
const modalTitle = () => {
if (typeof props.title === 'string') {
return props.title;
}
return <props.title model={props.model} items={props.items}></props.title>;
};
const modalTrigger = () => {
if (!props.trigger) {
return null;
}
if (typeof props.trigger === 'function') {
return <props.trigger model={props.model} items={props.items} open={open}></props.trigger>;
}
const internal = {
text: '新增',
buttonProps: {},
buttonSlots: {},
};
if (typeof props.trigger === 'string') {
internal.text = props.trigger;
}
if (typeof props.trigger === 'object') {
Object.assign(internal, props.trigger);
}
return (
<Button type="primary" {...internal.buttonProps} onClick={open}>
{{
...internal.buttonSlots,
icon: () => <i class="icon-park-outline-add"></i>,
default: () => internal.text,
}}
</Button>
);
};
const submitForm = async () => {
if (await anFormRef.value?.validate()) {
return;
}
try {
loading.value = true;
const data = getModel(model.value);
const res = await (props as any).submit?.(data, props.items);
const msg = res?.data?.message;
msg && Message.success(msg);
visible.value = false;
emit('submited', res);
} catch {
// todo
} finally {
loading.value = false;
}
};
const open = async (data: Recordable = {}) => {
visible.value = true;
await nextTick();
model.value = cloneDeep(originModel)
setModel(model.value, data);
};
const close = () => {
loading.value = false;
visible.value = false;
};
const onClose = () => {};
const context: AnFormModalContext = {
visible,
loading,
anFormRef,
open,
close,
onClose,
submitForm,
modalTitle,
modalTrigger,
};
provide(AnFormModalContextKey, context);
return {
...context
};
},
render() {
return (
<>
<this.modalTrigger></this.modalTrigger>
<Modal
titleAlign="start"
closable={false}
{...this.modalProps}
v-model:visible={this.visible}
class="an-form-modal"
maskClosable={false}
unmountOnClose={true}
onClose={this.onClose}
>
{{
title: this.modalTitle,
default: () => (
<AnForm
ref="formRef"
model={this.model}
onUpdate:model={v => this.$emit('update:model', v)}
items={this.items}
formProps={this.formProps}
></AnForm>
),
footer: () => (
<div class="flex items-center justify-between gap-4">
<div></div>
<div class="space-x-2">
<Button onClick={() => (this.visible = false)}></Button>
<Button type="primary" loading={this.loading} onClick={this.submitForm}>
</Button>
</div>
</div>
),
}}
</Modal>
</>
);
},
});
export type AnFormModalTitle = string | ((model: Recordable, items: AnFormItemProps[]) => any);
export type AnFormModalTrigger =
| boolean
| string
| ((model: Recordable, items: AnFormItemProps[]) => any)
| {
text?: string;
buttonProps?: ButtonInstance['$props'];
buttonSlots?: Recordable;
};
export type AnFormModalInstance = InstanceType<typeof AnFormModal>;
export type AnFormModalProps = Pick<
AnFormModalInstance['$props'],
'title' | 'trigger' | 'modalProps' | 'model' | 'items' | 'submit' | 'formProps'
>;