141 lines
3.0 KiB
Vue
Raw Normal View History

2025-10-31 16:16:56 +08:00
<template>
2025-11-03 17:56:26 +08:00
<el-dialog
:visible.sync="visible"
:title="title"
:width="width"
destroy-on-close
>
2026-04-08 15:47:57 +08:00
<template #title>
<div class="dialog-header" v-if="tagContent">
<span>{{ title }}</span>
<div class="dialog-header-extra">
<el-tag :type="tagType">{{ tagContent }}</el-tag>
</div>
</div>
<template v-else>{{ title }}</template>
</template>
2025-11-03 17:56:26 +08:00
<component
v-if="dynamicComponent"
:is="dynamicComponent"
ref="dynamicComponentRef"
v-bind="componentProps"
/>
2025-11-13 17:03:24 +08:00
<slot></slot>
2025-10-31 16:16:56 +08:00
<template #footer>
2026-04-08 15:47:57 +08:00
<div class="dialog-footer" :class="footerClass">
<el-button class="button" size="large" type="primary" @click="onConfirm"> {{ onConfirmName || '保存' }} </el-button>
<el-button class="button" size="large" :type="onCancelType" @click="onCancel"> {{ onCancelName || '取消' }} </el-button>
2025-10-31 16:16:56 +08:00
</div>
</template>
</el-dialog>
</template>
<script setup>
2026-04-08 15:47:57 +08:00
import { computed, ref, markRaw } from "vue";
2025-11-03 17:56:26 +08:00
const dynamicComponentRef = ref(null);
defineExpose({
dynamicComponentRef // 暴露给父组件
});
2025-10-31 16:16:56 +08:00
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
width: {
type: String,
default: "50%",
},
title: {
type: String,
default: "",
},
2025-11-03 17:56:26 +08:00
dynamicComponent: {
type: [Object, Function],
default: null,
},
componentProps: {
type: Object,
default: () => ({}),
},
2025-10-31 16:16:56 +08:00
onConfirm: {
type: Function,
default: () => {},
},
onCancel: {
type: Function,
default: () => {},
},
onConfirmName: {
type: String,
2025-11-14 14:58:33 +08:00
default: "保存",
2026-04-08 15:47:57 +08:00
validator: (value) => value === null || typeof value === 'string'
},
onCancelName: {
type: String,
default: "取消",
2026-04-08 15:47:57 +08:00
validator: (value) => value === null || typeof value === 'string'
},
onCancelType: {
type: String,
default: null,
},
tagContent: {
type: String,
default: ""
},
tagType: {
type: String,
default: ""
},
footerPosition: {
type: String,
default: "center",
validator: (value) => value === null || ['center', 'flex-end', 'flex-start'].includes(value)
}
2025-10-31 16:16:56 +08:00
});
2025-11-03 17:56:26 +08:00
const normalizedComponent = computed(() =>
props.dynamicComponent ? markRaw(props.dynamicComponent) : null
);
2026-04-08 15:47:57 +08:00
const footerClass = computed(() => {
if (!props.footerPosition) {
return 'footer-center';
}
return `footer-${props.footerPosition}`;
});
2025-10-31 16:16:56 +08:00
</script>
2025-11-14 14:58:33 +08:00
<style lang="scss" scoped>
2026-04-08 15:47:57 +08:00
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.dialog-header-extra {
margin-left: auto;
}
}
2025-11-14 14:58:33 +08:00
.dialog-footer {
display: flex;
2026-04-08 15:47:57 +08:00
&.footer-center {
justify-content: center;
}
&.footer-flex-end {
justify-content: flex-end;
}
&.footer-flex-start {
justify-content: flex-start;
}
2025-11-14 14:58:33 +08:00
.button {
width: 150px;
}
}
2025-10-31 16:16:56 +08:00
</style>