改造jsp项目的alert框和confirm框

背景

之前项目的模态框改造完成,业务也想把页面中的提示框和确认框也改造一下;这里记录一下改造中的细节。

之前项目中的提示框和确认框用的是浏览器自带的 alertconfirm。改造之前无法支持业务一些复杂一点的需求,遂将之改造;

实现

弹框层级设计参考了 elementantd,默认样式也是复用了他们的样式。层级从父到子如下所示

messageBoxWrap 是弹出框包裹层和遮罩层。messageBox 是弹出框本身,里面包含了标题,内容,按钮。

而且因为默认 confirm 框会有返回值,所以这里改造后的也就返回了一个 Promise,并且只会 resolve(true) 或者 resolve(false)

js 复制代码
// type 区分是 alert 还是 confirm
// paramSting 表示提示框的内容,项目中没有自定义标题的需求,所以这里提示框都用统一的标题
function showMessagebox(type, paramString) {
  //获取项目顶层的document,弹框显示需要覆盖顶层页面
  var topDoc = window.top.document;
  var mainFrameSet = topDoc.querySelector("#setMain");
  var mainFrameSetParent = mainFrameSet.parentNode;
  var topDocBody = window.top.document.body;
  //创建弹框盒子,添加遮罩层
  return new Promise((resolve, reject) => {
      // 具体实现
  });
}

注意事项

有几个注意的点是

  • 创建弹框时需要禁用键盘事件,弹框消失后启用键盘事件,防止弹框还未消失,使用 tab+enter去了其他页面的情况
  • 需要避免有一个弹框存在的情况下,重复点击创建弹框

然后是具体实现。

添加默认样式

js 复制代码
/*弹框的默认样式;*/
var messageBoxWrap_class = "modernMessagebox";
var messageBoxWrap_style =
  "width:100%;height:100%;position:fixed;top:0;left:0;text-align:center;";
var messageBox_style =
  "user-select:none;display: inline-block;width: 420px;padding: 10px 0px 20px;vertical-align: middle;background-color: #fff;border-radius: 4px;border: 1px solid #ebeef5;font-size:18px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);text-align: left;";
var header_style =
  "padding: 10px 16px 10px;font-size:14px;display:flex;justify-content: space-between;";
var content_style = "padding: 10px 15px;color: #606266;font-size: 14px;";
var footer_style = "padding: 5px 15px 0;text-align: right;";
var button_style =
  "display: inline-block;line-height: 1;wite-space: nowrap;cursor: pointer;background: #fff;border: 1px solid #dcdfe6;color: #606266;-webkit-appearance: none;text-align:center;box-sizing:border-box;outline:0;margin:0;transition:.1s;font-weight:500;padding:10px 16px;font-size:12px;border-radius:3px;";
var button_primary =
  "color: #fff;background: rgb(11, 87, 208);border: 1px solid rgb(11, 87, 208);border-radius: 20px 20px;";

创建 messageBoxWrap,添加遮罩层

js 复制代码
var messageBoxWrap = document.createElement("div");
messageBoxWrap.setAttribute("tabindex", "-1"); // 设置使其不能通过tab键聚焦
messageBoxWrap.className = messageBoxWrap_class + Date.now(); // 每次弹框的类名都是唯一的

// 使 messageBoxWrap 覆盖在顶层 window 上
messageBoxWrap.style = messageBoxWrap_style + ";width:" + topDocBody.clientWidth + "px;";

创建 messageBox,添加 header,content,footer

1、创建messageBox

js 复制代码
var messageBox = document.createElement("div");
messageBox.className = "messageBox";
messageBox.style = messageBox_style;

2、创建 header

js 复制代码
//创建 header
var header = document.createElement("div");
header.className = "messageBox_header";
header.style = header_style;
var headerText = document.createElement("div");
headerText.textContent = "提示";
var headerBtn = document.createElement("div");
headerBtn.style = "cursor:pointer;text-align:right;";
headerBtn.textContent = "x";
headerBtn.onclick = function () {
  mainFrameSetParent.removeChild(messageBoxWrap);
  enableKeyboard();
};

当点击头部的关闭按钮时,在顶层文档对象(mainFrameSetParent)中移除弹框。这里 enableKeyboard 表示移除弹框后恢复键盘事件。因为有时候业务操作飞快,有时候因为电脑卡,弹框还没完全关闭,使用键盘操作去到了其他页面,导致弹框显示在了其他页面。

3、创建 content 包裹层

js 复制代码
//创建 content 包裹层
var contentBox = document.createElement("div");
contentBox.className = "messageBox_content";
contentBox.style = content_style;
contentBox.textContent = paramString;

4、创建 footer

这里如果使 confirm 框,则多一个取消按钮

js 复制代码
//创建 footer
var footer = document.createElement("div");
footer.className = "messageBox_footer";
footer.style = footer_style;
var yesBtn = document.createElement("button");
yesBtn.style = button_style + button_primary;
yesBtn.textContent = type === "confirm" ? "确认" : "确定";
yesBtn.onclick = function () {
  if (type === "alert") {
    if (currentAlertBoxCount > 0) {
      currentAlertBoxCount--;
    }
  }
  resolve(true);
  mainFrameSetParent.removeChild(messageBoxWrap);
  enableKeyboard(); // 关闭弹框后恢复键盘事件
};
if (type === "confirm") {
  var noBtn = document.createElement("button");
  noBtn.style =
    button_style + "margin-left: 10px;border-radius: 20px 20px;";
  noBtn.textContent = "取消";
  noBtn.onclick = function () {
    resolve(false);
    mainFrameSetParent.removeChild(messageBoxWrap);
    enableKeyboard(); // 关闭弹框后恢复键盘事件
  };
}
footer.appendChild(yesBtn);
if (type === "confirm") {
  footer.appendChild(noBtn);
}

5、将创建的弹框插入顶层文档

创建弹框时,使焦点聚焦到弹框,并且禁用键盘事件。

js 复制代码
messageBox.appendChild(header);
messageBox.appendChild(contentBox);
messageBox.appendChild(footer);
messageBoxWrap.appendChild(messageBox);
//将创建好的弹框插入顶层 document
mainFrameSetParent.appendChild(messageBoxWrap);
messageBoxWrap.focus();
disableKeyboard();

6、页面调用弹出框

设置 currentAlertBoxCount 代表 alert 框的数量,避免同时出现多个弹框

js 复制代码
var currentAlertBoxCount = 0;
async function modernConfirm(str) {
  return await showMessagebox("confirm", str);
}
async function modernAlert(str) {
  if(currentAlertBoxCount>0){
    return
  }
  currentAlertBoxCount++;
  return await showMessagebox("alert", str);
}

完整代码

js 复制代码
/*弹框的默认样式;*/
var messageBoxWrap_class = "modernMessagebox";
var messageBoxWrap_style =
  "width:100%;height:100%;position:fixed;top:0;left:0;text-align:center;";
var messageBox_style =
  "user-select:none;display: inline-block;width: 420px;padding: 10px 0px 20px;vertical-align: middle;background-color: #fff;border-radius: 4px;border: 1px solid #ebeef5;font-size:18px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);text-align: left;";
var header_style =
  "padding: 10px 16px 10px;font-size:14px;display:flex;justify-content: space-between;";
var content_style = "padding: 10px 15px;color: #606266;font-size: 14px;";
var footer_style = "padding: 5px 15px 0;text-align: right;";
var button_style =
  "display: inline-block;line-height: 1;wite-space: nowrap;cursor: pointer;background: #fff;border: 1px solid #dcdfe6;color: #606266;-webkit-appearance: none;text-align:center;box-sizing:border-box;outline:0;margin:0;transition:.1s;font-weight:500;padding:10px 16px;font-size:12px;border-radius:3px;";
var button_primary =
  "color: #fff;background: rgb(11, 87, 208);border: 1px solid rgb(11, 87, 208);border-radius: 20px 20px;";
var currentAlertBoxCount = 0;
//弹框时禁用键盘事件,弹框消失后启用键盘事件,防止弹框还未消失,使用 tab+enter去了其他页面的情况
let keyboardDisabled = false;
function disableKeyboard() {
  keyboardDisabled = true;
  document.addEventListener("keydown", handleKeyDown);
}
function enableKeyboard() {
  keyboardDisabled = false;
  document.removeEventListener("keydown", handleKeyDown);
}
function handleKeyDown(event) {
  if (keyboardDisabled) {
    event.preventDefau1t();
  }
}
// type 区分是 alert 还是 confirm
// paramSting 表示提示框的内容,项目中没有自定义标题的需求,所以这里提示框都用统一的标题
function showMessagebox(type, paramString) {
  //获取项目顶层的document,弹框显示需要覆盖顶层页面
  var topDoc = window.top.document;
  var mainFrameSet = topDoc.querySelector("#setMain");
  var mainFrameSetParent = mainFrameSet.parentNode;
  var topDocBody = window.top.document.body;
  //创建弹框盒子,添加遮罩层
  return new Promise((resolve, reject) => {
    var messageBoxWrap = document.createElement("div");
    messageBoxWrap.setAttribute("tabindex", "-1");
    messageBoxWrap.className = messageBoxWrap_class + Date.now();
    messageBoxWrap.style = messageBoxWrap_style + ";width:" + topDocBody.clientWidth + "px;";
    var messageBox = document.createElement("div");
    messageBox.className = "messageBox";
    messageBox.style = messageBox_style;
    //创建 header
    var header = document.createElement("div");
    header.className = "messageBox_header";
    header.style = header_style;
    var headerText = document.createElement("div");
    headerText.textContent = "提示";
    var headerBtn = document.createElement("div");
    headerBtn.style = "cursor:pointer;text-align:right;";
    headerBtn.textContent = "x";
    headerBtn.onclick = function () {
      mainFrameSetParent.removeChild(messageBoxWrap);
      enableKeyboard();
    };
    header.appendChild(headerText);
    header.appendChild(headerBtn);
    //创建 content 包裹层
    var contentBox = document.createElement("div");
    contentBox.className = "messageBox_content";
    contentBox.style = content_style;
    contentBox.textContent = paramString;
    //创建 footer
    var footer = document.createElement("div");
    footer.className = "messageBox_footer";
    footer.style = footer_style;
    var yesBtn = document.createElement("button");
    yesBtn.style = button_style + button_primary;
    yesBtn.textContent = type === "confirm" ? "确认" : "确定";
    yesBtn.onclick = function () {
      if (type === "alert") {
        if (currentAlertBoxCount > 0) {
          currentAlertBoxCount--;
        }
      }
      resolve(true);
      mainFrameSetParent.removeChild(messageBoxWrap);
      enableKeyboard();
    };
    if (type === "confirm") {
      var noBtn = document.createElement("button");
      noBtn.style =
        button_style + "margin-left: 10px;border-radius: 20px 20px;";
      noBtn.textContent = "取消";
      noBtn.onclick = function () {
        resolve(false);
        mainFrameSetParent.removeChild(messageBoxWrap);
        enableKeyboard();
      };
    }
    footer.appendChild(yesBtn);
    if (type === "confirm") {
      footer.appendChild(noBtn);
    }

    messageBox.appendChild(header);
    messageBox.appendChild(contentBox);
    messageBox.appendChild(footer);
    messageBoxWrap.appendChild(messageBox);
    //将创建好的弹框插入顶层 document
    mainFrameSetParent.appendChild(messageBoxWrap);
    messageBoxWrap.focus();
    disableKeyboard();
  });
}
async function modernConfirm(str) {
  return await showMessagebox("confirm", str);
}
async function modernAlert(str) {
  if(currentAlertBoxCount>0){
    return
  }
  currentAlertBoxCount++;
  return await showMessagebox("alert", str);
}
相关推荐
前端大卫6 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘22 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare23 分钟前
浅浅看一下设计模式
前端
Lee川26 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端