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: () => {},
|
|
|
|
|
},
|
2025-11-13 17:12:27 +08:00
|
|
|
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'
|
2025-11-13 17:12:27 +08:00
|
|
|
},
|
|
|
|
|
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-11-13 17:12:27 +08:00
|
|
|
}
|
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>
|