vue3封装alert 提示组件 仿element-plus

这样的提示

我直接上代码了。组件如下

复制代码
<template>
	<div class="smart-alert" :class="alertClasses">
		<div class="alert-content">
			<div class="alert-icon">
				<slot name="icon">
					<component :is="typeIcon" />
				</slot>
			</div>
			<div class="alert-message">
				<div class="alert-title" v-if="title || $slots.title">
					<slot name="title">
						{{ title }}
					</slot>
				</div>
				<div class="alert-description">
					<slot>
						{{ message }}
					</slot>
				</div>
			</div>
		</div>

		<div class="alert-actions">
			<slot name="action">
				<el-button v-if="actionText" :type="actionType" link @click="handleAction">
					{{ actionText }}
				</el-button>
			</slot>

			<el-button v-if="closable" link class="alert-close" @click="handleClose">
				<el-icon><Close /></el-icon>
			</el-button>
		</div>
	</div>
</template>

<script setup>
import { computed } from 'vue';
import { ElIcon, ElButton } from 'element-plus';
import { InfoFilled, SuccessFilled, WarningFilled, CircleCloseFilled, Close } from '@element-plus/icons-vue';

const props = defineProps({
	// 提示类型
	type: {
		type: String,
		default: 'info',
		validator: (value) => ['info', 'success', 'warning', 'error'].includes(value),
	},
	// 标题
	title: {
		type: String,
		default: '',
	},
	// 消息内容
	message: {
		type: String,
		default: '',
	},
	// 操作按钮文字
	actionText: {
		type: String,
		default: '',
	},
	// 操作按钮类型
	actionType: {
		type: String,
		default: 'primary',
	},
	// 是否可关闭
	closable: {
		type: Boolean,
		default: false,
	},
	// 是否显示图标
	showIcon: {
		type: Boolean,
		default: true,
	},
	// 自定义类名
	customClass: {
		type: String,
		default: '',
	},
});

const emit = defineEmits(['action', 'close']);

const typeConfig = {
	info: { icon: InfoFilled, color: '#409eff', bg: 'linear-gradient(135deg, #ecf5ff 0%, #f0f7ff 100%)', border: '#d4e3ff' },
	success: { icon: SuccessFilled, color: '#67c23a', bg: 'linear-gradient(135deg, #f0f9eb 0%, #f5fbf0 100%)', border: '#e1f3d8' },
	warning: { icon: WarningFilled, color: '#e6a23c', bg: 'linear-gradient(135deg, #fdf6ec 0%, #fef9f0 100%)', border: '#faecd8' },
	error: { icon: CircleCloseFilled, color: '#f56c6c', bg: 'linear-gradient(135deg, #fef0f0 0%, #fef5f5 100%)', border: '#fde2e2' },
};

const typeIcon = computed(() => typeConfig[props.type].icon);

const alertClasses = computed(() => {
	return [
		`smart-alert--${props.type}`,
		{
			'smart-alert--closable': props.closable,
			'smart-alert--no-icon': !props.showIcon,
		},
		props.customClass,
	];
});

const alertStyles = computed(() => {
	const config = typeConfig[props.type];
	return {
		'--alert-color': config.color,
		'--alert-bg': config.bg,
		'--alert-border': config.border,
	};
});

const handleAction = () => {
	emit('action');
};

const handleClose = () => {
	emit('close');
};
</script>

<style scoped lang="less">
.smart-alert {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	padding: 16px;
	background: var(--alert-bg, linear-gradient(135deg, #ecf5ff 0%, #f0f7ff 100%));
	border: 1px solid var(--alert-border, #d4e3ff);
	border-radius: 8px;
	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
	transition: all 0.3s ease;

	.alert-content {
		display: flex;
		align-items: flex-start;
		flex: 1;
		gap: 12px;

		.alert-icon {
			display: flex;
			align-items: center;
			color: var(--alert-color, #409eff);
			font-size: 18px;
			margin-top: 1px;
			flex-shrink: 0;

			:deep(svg) {
				width: 1em;
				height: 1em;
			}
		}

		.alert-message {
			flex: 1;

			.alert-title {
				color: var(--alert-color, #409eff);
				font-size: 14px;
				font-weight: 600;
				line-height: 1.4;
				margin-bottom: 4px;
			}

			.alert-description {
				color: #606266;
				font-size: 14px;
				line-height: 1.5;
				word-break: break-word;

				:deep(*) {
					margin: 0;
				}
			}
		}
	}

	.alert-actions {
		display: flex;
		align-items: flex-start;
		gap: 8px;
		flex-shrink: 0;
		margin-left: 12px;

		:deep(.el-button) {
			color: var(--alert-color, #409eff);
			font-size: 14px;
			padding: 4px 8px;
			height: auto;

			&:hover {
				background: rgba(var(--alert-color-rgb), 0.1);
			}

			&.alert-close {
				color: #909399;

				&:hover {
					background: rgba(144, 147, 153, 0.1);
				}
			}
		}
	}

	// 无图标样式
	&.smart-alert--no-icon {
		.alert-content {
			.alert-icon {
				display: none;
			}
		}
	}
}

// 类型样式
.smart-alert--info {
	--alert-color: #409eff;
	--alert-bg: linear-gradient(135deg, #ecf5ff 0%, #f0f7ff 100%);
	--alert-border: #d4e3ff;
}

.smart-alert--success {
	--alert-color: #67c23a;
	--alert-bg: linear-gradient(135deg, #f0f9eb 0%, #f5fbf0 100%);
	--alert-border: #e1f3d8;
}

.smart-alert--warning {
	--alert-color: #e6a23c;
	--alert-bg: linear-gradient(135deg, #fdf6ec 0%, #fef9f0 100%);
	--alert-border: #faecd8;
}

.smart-alert--error {
	--alert-color: #f56c6c;
	--alert-bg: linear-gradient(135deg, #fef0f0 0%, #fef5f5 100%);
	--alert-border: #fde2e2;
}
</style>

父组件中使用

复制代码
<SmartAlert 
    type="success"
    title="操作成功" 
    message="您的作品已成功发布" 
    action-text="查看作品"
    closable
    @action="handleViewWork"
    @close="handleClose"
  />

当然以上涉及的方法都可以不用的

也可以只显示一个 message 信息

相关推荐
IT_陈寒2 小时前
SpringBoot实战避坑指南:我在微服务项目中总结的12条高效开发经验
前端·人工智能·后端
华洛2 小时前
解读麦肯锡报告:Agent落地的六大经验教训
前端·javascript·产品经理
艾小码2 小时前
还在重复造轮子?掌握这7个原则,让你的Vue组件复用性飙升!
前端·javascript·vue.js
探索宇宙真理.2 小时前
React Native Community CLI命令执行 | CVE-2025-11953 复现&研究
javascript·经验分享·react native·react.js·安全漏洞
. . . . .3 小时前
React底层原理
javascript·react.js
2401_831501733 小时前
Web网页之前端三剑客汇总篇(基础版)
前端
木易 士心4 小时前
Vue 3 Props 响应式深度解析:从原理到最佳实践
前端·javascript·vue.js
海鸥两三7 小时前
uniapp 小程序引入 uview plus 框架,获得精美的UI框架
前端·vue.js·ui·小程序·uni-app
lightgis8 小时前
16openlayers加载COG(云优化Geotiff)
前端·javascript·html·html5