|
|
/*
|
|
|
* vmessage.js
|
|
|
* Copyright (C) 2025 veypi <i@veypi.com>
|
|
|
*
|
|
|
* Distributed under terms of the MIT license.
|
|
|
*/
|
|
|
|
|
|
class Message {
|
|
|
constructor() {
|
|
|
this.container = null;
|
|
|
this.init();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 初始化容器
|
|
|
*/
|
|
|
init() {
|
|
|
this.container = document.createElement('div');
|
|
|
this.container.className = 'message-container';
|
|
|
document.body.appendChild(this.container);
|
|
|
|
|
|
// 添加基础样式
|
|
|
this.addBaseStyles();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 添加基础样式
|
|
|
*/
|
|
|
addBaseStyles() {
|
|
|
const style = document.createElement('style');
|
|
|
style.textContent = `
|
|
|
.message-container {
|
|
|
position: fixed;
|
|
|
top: 60px;
|
|
|
left: 50%;
|
|
|
transform: translateX(-50%);
|
|
|
z-index: 9999;
|
|
|
width: 300px;
|
|
|
}
|
|
|
|
|
|
.message-item {
|
|
|
margin-bottom: 10px;
|
|
|
padding: 15px;
|
|
|
border-radius: 4px;
|
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
transform: translateY(100%);
|
|
|
opacity: 0;
|
|
|
transition: all 0.3s ease;
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
}
|
|
|
|
|
|
.message-item.show {
|
|
|
transform: translateY(0);
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
.message-icon {
|
|
|
margin-right: 10px;
|
|
|
font-size: 16px;
|
|
|
line-height: 1;
|
|
|
}
|
|
|
|
|
|
.message-content {
|
|
|
flex: 1;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.message-close {
|
|
|
margin-left: 10px;
|
|
|
cursor: pointer;
|
|
|
font-size: 16px;
|
|
|
opacity: 0.7;
|
|
|
transition: opacity 0.2s;
|
|
|
}
|
|
|
|
|
|
.message-close:hover {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
.message-success {
|
|
|
background-color: #f0f9eb;
|
|
|
border: 1px solid #e1f3d8;
|
|
|
color: var(--color-success, #67c23a);
|
|
|
}
|
|
|
|
|
|
.message-warning {
|
|
|
background-color: #fdf6ec;
|
|
|
border: 1px solid #faecd8;
|
|
|
color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
.message-error {
|
|
|
background-color: #fef0f0;
|
|
|
border: 1px solid #fde2e2;
|
|
|
color: #f56c6c;
|
|
|
}
|
|
|
|
|
|
.message-info {
|
|
|
background-color: #edf2fc;
|
|
|
border: 1px solid #ebeef5;
|
|
|
color: #409eff;
|
|
|
}
|
|
|
|
|
|
.prompt-overlay {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
z-index: 10000;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.prompt-overlay.show {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
.prompt-dialog {
|
|
|
background: white;
|
|
|
border-radius: 8px;
|
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
|
min-width: 400px;
|
|
|
max-width: 500px;
|
|
|
transform: scale(0.8);
|
|
|
transition: transform 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.prompt-overlay.show .prompt-dialog {
|
|
|
transform: scale(1);
|
|
|
}
|
|
|
|
|
|
.prompt-header {
|
|
|
padding: 20px 20px 10px;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
border-bottom: 1px solid #eee;
|
|
|
}
|
|
|
|
|
|
.prompt-title {
|
|
|
font-size: 18px;
|
|
|
font-weight: 500;
|
|
|
margin: 0;
|
|
|
}
|
|
|
|
|
|
.prompt-close {
|
|
|
cursor: pointer;
|
|
|
font-size: 20px;
|
|
|
color: #999;
|
|
|
border: none;
|
|
|
background: none;
|
|
|
padding: 0;
|
|
|
width: 20px;
|
|
|
height: 20px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.prompt-close:hover {
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.prompt-body {
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
.prompt-content {
|
|
|
margin-bottom: 20px;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.5;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.prompt-input {
|
|
|
width: 100%;
|
|
|
padding: 10px 12px;
|
|
|
border: 1px solid #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
font-size: 14px;
|
|
|
box-sizing: border-box;
|
|
|
transition: border-color 0.2s;
|
|
|
}
|
|
|
|
|
|
.prompt-input:focus {
|
|
|
outline: none;
|
|
|
border-color: #409eff;
|
|
|
}
|
|
|
|
|
|
.prompt-footer {
|
|
|
padding: 15px 20px;
|
|
|
text-align: right;
|
|
|
border-top: 1px solid #eee;
|
|
|
}
|
|
|
|
|
|
.prompt-btn {
|
|
|
padding: 8px 16px;
|
|
|
border: 1px solid #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
font-size: 14px;
|
|
|
cursor: pointer;
|
|
|
margin-left: 10px;
|
|
|
transition: all 0.2s;
|
|
|
}
|
|
|
|
|
|
.prompt-btn-cancel {
|
|
|
background: white;
|
|
|
color: #606266;
|
|
|
}
|
|
|
|
|
|
.prompt-btn-cancel:hover {
|
|
|
background: #f5f7fa;
|
|
|
border-color: #c0c4cc;
|
|
|
}
|
|
|
|
|
|
.prompt-btn-confirm {
|
|
|
background: #409eff;
|
|
|
color: white;
|
|
|
border-color: #409eff;
|
|
|
}
|
|
|
|
|
|
.prompt-btn-confirm:hover {
|
|
|
background: #66b1ff;
|
|
|
border-color: #66b1ff;
|
|
|
}
|
|
|
`;
|
|
|
document.head.appendChild(style);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建消息元素
|
|
|
* @param {string} type - 消息类型 (success, warning, error, info)
|
|
|
* @param {string} content - 消息内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
createMessage(type, content, options = {}) {
|
|
|
const {
|
|
|
duration = 3000,
|
|
|
showClose = true,
|
|
|
onClose = null
|
|
|
} = options;
|
|
|
|
|
|
const messageItem = document.createElement('div');
|
|
|
messageItem.className = `message-item message-${type}`;
|
|
|
|
|
|
// 消息图标
|
|
|
const icons = {
|
|
|
success: '✓',
|
|
|
warning: '⚠',
|
|
|
error: '✕',
|
|
|
info: 'ℹ'
|
|
|
};
|
|
|
|
|
|
const icon = document.createElement('span');
|
|
|
icon.className = 'message-icon';
|
|
|
icon.textContent = icons[type] || icons.info;
|
|
|
|
|
|
// 消息内容
|
|
|
const contentEl = document.createElement('div');
|
|
|
contentEl.className = 'message-content';
|
|
|
contentEl.textContent = content;
|
|
|
|
|
|
messageItem.appendChild(icon);
|
|
|
messageItem.appendChild(contentEl);
|
|
|
|
|
|
// 关闭按钮
|
|
|
if (showClose) {
|
|
|
const closeBtn = document.createElement('span');
|
|
|
closeBtn.className = 'message-close';
|
|
|
closeBtn.innerHTML = '×';
|
|
|
closeBtn.onclick = () => {
|
|
|
this.removeMessage(messageItem);
|
|
|
if (onClose) onClose();
|
|
|
};
|
|
|
messageItem.appendChild(closeBtn);
|
|
|
}
|
|
|
|
|
|
this.container.appendChild(messageItem);
|
|
|
|
|
|
// 显示动画
|
|
|
setTimeout(() => {
|
|
|
messageItem.classList.add('show');
|
|
|
}, 10);
|
|
|
|
|
|
// 自动关闭
|
|
|
if (duration > 0) {
|
|
|
setTimeout(() => {
|
|
|
this.removeMessage(messageItem);
|
|
|
if (onClose) onClose();
|
|
|
}, duration);
|
|
|
}
|
|
|
|
|
|
return messageItem;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 移除消息
|
|
|
* @param {HTMLElement} messageItem - 消息元素
|
|
|
*/
|
|
|
removeMessage(messageItem) {
|
|
|
if (messageItem && messageItem.parentNode) {
|
|
|
messageItem.classList.remove('show');
|
|
|
setTimeout(() => {
|
|
|
if (messageItem.parentNode) {
|
|
|
messageItem.parentNode.removeChild(messageItem);
|
|
|
}
|
|
|
}, 300);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 成功消息
|
|
|
* @param {string} content - 消息内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
success(content, options = {}) {
|
|
|
return this.createMessage('success', content, options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 警告消息
|
|
|
* @param {string} content - 消息内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
warning(content, options = {}) {
|
|
|
return this.createMessage('warning', content, options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 错误消息
|
|
|
* @param {string} content - 消息内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
error(content, options = {}) {
|
|
|
return this.createMessage('error', content, options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 信息消息
|
|
|
* @param {string} content - 消息内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
info(content, options = {}) {
|
|
|
return this.createMessage('info', content, options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 显示提示框
|
|
|
* @param {string} content - 提示内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
_prompt(content, options = {}) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const {
|
|
|
title = '提示',
|
|
|
type = 'confirm', // confirm, input
|
|
|
inputValue = '',
|
|
|
confirmText = '确定',
|
|
|
cancelText = '取消',
|
|
|
onConfirm = null,
|
|
|
onCancel = () => { resolve('') }
|
|
|
} = options;
|
|
|
|
|
|
// 创建遮罩层
|
|
|
const overlay = document.createElement('div');
|
|
|
overlay.className = 'prompt-overlay';
|
|
|
|
|
|
// 创建对话框
|
|
|
const dialog = document.createElement('div');
|
|
|
dialog.className = 'prompt-dialog';
|
|
|
|
|
|
// 头部
|
|
|
const header = document.createElement('div');
|
|
|
header.className = 'prompt-header';
|
|
|
|
|
|
const titleEl = document.createElement('h3');
|
|
|
titleEl.className = 'prompt-title';
|
|
|
titleEl.textContent = title;
|
|
|
|
|
|
const closeBtn = document.createElement('button');
|
|
|
closeBtn.className = 'prompt-close';
|
|
|
closeBtn.innerHTML = '×';
|
|
|
|
|
|
header.appendChild(titleEl);
|
|
|
header.appendChild(closeBtn);
|
|
|
|
|
|
// 主体
|
|
|
const body = document.createElement('div');
|
|
|
body.className = 'prompt-body';
|
|
|
|
|
|
const contentEl = document.createElement('div');
|
|
|
contentEl.className = 'prompt-content';
|
|
|
contentEl.textContent = content;
|
|
|
|
|
|
body.appendChild(contentEl);
|
|
|
|
|
|
// 输入框(如果是input类型)
|
|
|
let inputEl = null;
|
|
|
if (type === 'input') {
|
|
|
inputEl = document.createElement('input');
|
|
|
inputEl.className = 'prompt-input';
|
|
|
inputEl.type = 'text';
|
|
|
inputEl.value = inputValue;
|
|
|
body.appendChild(inputEl);
|
|
|
}
|
|
|
|
|
|
// 底部按钮
|
|
|
const footer = document.createElement('div');
|
|
|
footer.className = 'prompt-footer';
|
|
|
|
|
|
const cancelBtn = document.createElement('button');
|
|
|
cancelBtn.className = 'prompt-btn prompt-btn-cancel';
|
|
|
cancelBtn.textContent = cancelText;
|
|
|
|
|
|
const confirmBtn = document.createElement('button');
|
|
|
confirmBtn.className = 'prompt-btn prompt-btn-confirm';
|
|
|
confirmBtn.textContent = confirmText;
|
|
|
|
|
|
footer.appendChild(cancelBtn);
|
|
|
footer.appendChild(confirmBtn);
|
|
|
|
|
|
dialog.appendChild(header);
|
|
|
dialog.appendChild(body);
|
|
|
dialog.appendChild(footer);
|
|
|
overlay.appendChild(dialog);
|
|
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
|
|
// 显示动画
|
|
|
setTimeout(() => {
|
|
|
overlay.classList.add('show');
|
|
|
}, 10);
|
|
|
|
|
|
// 如果是input类型,自动聚焦
|
|
|
if (inputEl) {
|
|
|
setTimeout(() => {
|
|
|
inputEl.focus();
|
|
|
}, 300);
|
|
|
}
|
|
|
|
|
|
// 事件处理
|
|
|
const closeDialog = (result = null) => {
|
|
|
overlay.classList.remove('show');
|
|
|
setTimeout(() => {
|
|
|
if (overlay.parentNode) {
|
|
|
overlay.parentNode.removeChild(overlay);
|
|
|
}
|
|
|
}, 300);
|
|
|
return result;
|
|
|
};
|
|
|
|
|
|
// 关闭按钮
|
|
|
closeBtn.onclick = () => {
|
|
|
closeDialog();
|
|
|
onCancel ? onCancel() : reject(new Error('cancelled'))
|
|
|
};
|
|
|
|
|
|
// 取消按钮
|
|
|
cancelBtn.onclick = () => {
|
|
|
closeDialog();
|
|
|
onCancel ? onCancel() : reject(new Error('cancelled'))
|
|
|
};
|
|
|
|
|
|
// 确认按钮
|
|
|
confirmBtn.onclick = () => {
|
|
|
const value = inputEl ? inputEl.value : true;
|
|
|
closeDialog();
|
|
|
resolve(value);
|
|
|
if (onConfirm) onConfirm(value);
|
|
|
};
|
|
|
|
|
|
// ESC键关闭
|
|
|
const handleEsc = (e) => {
|
|
|
if (e.key === 'Escape') {
|
|
|
closeDialog();
|
|
|
onCancel ? onCancel() : reject(new Error('cancelled'))
|
|
|
document.removeEventListener('keydown', handleEsc);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
document.addEventListener('keydown', handleEsc);
|
|
|
|
|
|
// 点击遮罩层关闭
|
|
|
overlay.onclick = (e) => {
|
|
|
if (e.target === overlay) {
|
|
|
closeDialog();
|
|
|
onCancel ? onCancel() : reject(new Error('cancelled'))
|
|
|
}
|
|
|
};
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 确认框
|
|
|
* @param {string} content - 确认内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
confirm(content, options = {}) {
|
|
|
return this._prompt(content, {
|
|
|
...options,
|
|
|
type: 'confirm'
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 输入框
|
|
|
* @param {string} content - 提示内容
|
|
|
* @param {Object} options - 配置选项
|
|
|
*/
|
|
|
prompt(message, content, options = {}) {
|
|
|
return this._prompt(message, {
|
|
|
...options,
|
|
|
type: 'input',
|
|
|
inputValue: content,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 创建单例实例
|
|
|
const message = new Message();
|
|
|
|
|
|
// 导出默认实例和类
|
|
|
export default message;
|
|
|
export { Message };
|