tmagic-editor/packages/form/src/FormDialog.vue

217 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<TMagicDialog
v-model="dialogVisible"
:class="['m-form-dialog', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
top="20px"
append-to-body
:title="title"
:width="width"
:zIndex="zIndex"
:fullscreen="fullscreen"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
:destroy-on-close="destroyOnClose"
:show-close="showClose"
@close="closeHandler"
>
<div
v-if="dialogVisible"
class="m-dialog-body"
:style="`max-height: ${bodyHeight}; overflow-y: auto; overflow-x: hidden;`"
>
<Form
v-model="stepActive"
ref="form"
:size="size"
:disabled="disabled"
:config="config"
:init-values="values"
:parent-values="parentValues"
:label-width="labelWidth"
:label-position="labelPosition"
:inline="inline"
:prevent-submit-default="preventSubmitDefault"
:use-field-text-in-error="useFieldTextInError"
:type-match-valid="typeMatchValid"
:extend-state="extendState"
:theme="effectiveTheme"
@change="changeHandler"
></Form>
<slot></slot>
</div>
<template #footer>
<TMagicRow class="dialog-footer">
<TMagicCol :span="12" style="text-align: left">
<div style="min-height: 1px">
<slot name="left"></slot>
</div>
</TMagicCol>
<TMagicCol :span="12">
<slot name="footer">
<TMagicButton v-if="showCancel" @click="cancel" size="small"> </TMagicButton>
<TMagicButton v-if="hasStep && stepActive > 1" type="info" size="small" @click="preStep"
>上一步</TMagicButton
>
<TMagicButton v-if="hasStep && stepCount > stepActive" type="info" size="small" @click="nextStep"
>下一步</TMagicButton
>
<TMagicButton v-else type="primary" size="small" :disabled="disabled" :loading="saveFetch" @click="save">{{
confirmText
}}</TMagicButton>
</slot>
</TMagicCol>
</TMagicRow>
</template>
</TMagicDialog>
</template>
<script setup lang="ts">
import { computed, inject, provide, ref } from 'vue';
import { M_THEME_KEY, TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
import Form from './Form.vue';
import { ContainerChangeEventData, FormConfig, FormState, FormValue, StepConfig } from './schema';
defineOptions({
name: 'MFormDialog',
});
const props = withDefaults(
defineProps<{
config?: FormConfig;
values?: Object;
parentValues?: Object;
width?: string | number;
labelWidth?: string;
/** 是否开启类型匹配校验 */
typeMatchValid?: boolean;
fullscreen?: boolean;
disabled?: boolean;
title?: string;
inline?: boolean;
labelPosition?: string;
zIndex?: number;
size?: 'small' | 'default' | 'large';
confirmText?: string;
preventSubmitDefault?: boolean;
closeOnClickModal?: boolean;
closeOnPressEscape?: boolean;
destroyOnClose?: boolean;
showClose?: boolean;
showCancel?: boolean;
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
useFieldTextInError?: boolean;
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
/**
* 主题名。优先级:传入 `theme` prop > 祖先 `provide(M_THEME_KEY)` > 空串。
* 计算结果会再次 `provide` 出去,使得 Dialog 被 Teleport 到 body 后,内部子树
* (包括 `MForm` 以及更深的 popover / dropdown仍能拿到主题。
*/
theme?: string;
}>(),
{
config: () => [],
values: () => ({}),
confirmText: '确定',
closeOnClickModal: false,
closeOnPressEscape: false,
destroyOnClose: false,
showClose: true,
showCancel: true,
useFieldTextInError: true,
},
);
const ancestorTheme = inject(M_THEME_KEY, null);
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
provide(M_THEME_KEY, effectiveTheme);
const emit = defineEmits(['close', 'submit', 'error', 'change']);
const form = ref<InstanceType<typeof Form>>();
const dialogVisible = ref(false);
const saveFetch = ref(false);
const stepActive = ref(1);
const bodyHeight = ref(`${document.body.clientHeight - 194}px`);
const stepCount = computed(() => {
if (!Array.isArray(props.config)) {
return 0;
}
for (const item of props.config) {
if ('type' in item && item.type === 'step') {
return (item as StepConfig).items.length;
}
}
return 0;
});
const hasStep = computed(() => {
if (!Array.isArray(props.config)) {
return false;
}
for (const item of props.config) {
if ('type' in item && item.type === 'step') {
return true;
}
}
return false;
});
const closeHandler = () => {
stepActive.value = 1;
emit('close');
};
const save = async () => {
try {
const changeRecords = [...(form.value?.changeRecords || [])];
const values = await form.value?.submitForm();
emit('submit', values, { changeRecords });
} catch (e) {
emit('error', e);
}
};
const preStep = () => {
stepActive.value -= 1;
};
const nextStep = () => {
stepActive.value += 1;
};
const changeHandler = (value: FormValue, eventData: ContainerChangeEventData) => {
emit('change', value, eventData);
};
const show = () => {
dialogVisible.value = true;
};
const hide = () => {
dialogVisible.value = false;
};
const cancel = () => {
hide();
};
defineExpose({
form,
saveFetch,
dialogVisible,
cancel,
save,
show,
hide,
});
</script>