mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-08-13 08:18:48 +00:00
217 lines
5.7 KiB
Vue
217 lines
5.7 KiB
Vue
<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>
|