弹窗 vs 抽屉 选用决策树
为避免滥用弹窗造成用户视野被强制打断,必须根据操作流程的“任务复杂度”和“上下文依赖度”进行精确选择:
| 容器类型 | 适用边界与场景 | 典型交互场景 | 核心设计理由与交互契约 |
|---|---|---|---|
| 模态弹窗 (Modal) |
1. 短小、即时、低干扰的任务。 2. 上下文弱依赖:不需要参照底层表格。 3. 字段数量少于 10 个,且无复杂分区。 |
密码修改、单项修改(重命名)、警示理由录入、新建精简表单、危急确认。 | 强力遮罩,强制阻断,使用户高聚焦于极小范围内,快速点击“确认/取消”退出。支持按 ESC 键退出(表单未变更时)。 |
| 侧边抽屉 (Drawer) |
1. 中长、深度编辑或配置类任务。 2. 强上下文依赖:需要一边看底层列表一边进行比对录入。 3. 包含大量标签页 (Tabs) 或包含分区折叠面板 (Collapse) 的大表单。 |
复杂详情查看、带有附表录入的申请单编辑、复杂审批流程记录、资源树授权配置。 | 从右侧平滑推入,保持左侧列表的视线,支持大幅垂直滚动排版,减少“新开详情页”的多标签割裂感。点击阴影背景默认不关闭。 |
尺寸与限宽规格
所有弹窗和抽屉必须统一映射全局宽度 Token,禁止开发人员在业务页面任意设置 style="width: 730px;" 等非标尺寸:
| 尺寸档位 | Modal 对应宽度 | Drawer 对应宽度 | 适用场景与排版规则 |
|---|---|---|---|
| 小 (SM) | 480px (.modal-sm) |
400px |
极简交互。通常为单列布局表单、提示弹窗。 |
| 中 (MD) | 720px (.modal-md) |
640px |
标准规格。支持完美的双列表单网格,是 80% 表单的默认尺寸。 |
| 大 (LG) | 960px (.modal-lg) |
800px |
宽幅内容。适用于内嵌表格、多列大表单、多 Tab 架构。 |
| 超大 (XL) | 1200px (.modal-xl) |
1024px |
极复杂系统。用于看板透视、内联大型表单或图形节点设计器。 |
防双重滚动垂直布局规范
为消灭屏幕内出现“双重滚动条”以及“确定按钮被挤出屏幕外”的低级缺陷,弹窗和抽屉必须遵循高度视口截断、Header/Footer 固底、Body 内部垂直滚动的设计契约:
Header 头部: 强固定,不随内容滚动
Body 内部:如果内容过长,仅在此区域内产生滚动条。
Footer 底部: 强固定,永远锁定于弹窗底部
/* ==========================================================================
防弹窗双重滚动核心 Flex 围堵布局 (已集成在全局样式,开发请务必遵循 DOM 层次)
========================================================================== */
.modal-dialog {
margin: 1.75rem auto;
}
.modal-content {
/* 强限制高度在当前视口内,扣减上下外边距 */
max-height: calc(100vh - 3.5rem);
display: flex !important;
flex-direction: column !important;
}
.modal-header, .modal-footer {
flex-shrink: 0; /* 拒绝被压缩 */
}
.modal-body {
overflow-y: auto; /* 仅在 body 区域产生独立滚动条 */
flex: 1; /* 自动撑满剩余高度 */
}
selResource 运维资源选择器
1. 组件定位与运行机制
selResource 是专为基础设施、运维节点及云服务等核心设备资产设计的高级筛选器。底层整合了 acmeAgGrid,采用弹窗表格分页加载模式(selectGrid),通过后端接口进行高速结构化字段检索,彻底杜绝了将数千条资源直接塞入下拉单选框导致的页面卡死灾难。
2. 何时使用 / 何时不要使用
- 需要在大量物理机、容器集群、网络交换机、IP 地址等运维资源中进行选取。
- 需要多列信息展示(如:资源名称、内网IP、区域、类型、运行状态)以防混淆。
- 涉及多选绑定,或者单表单有大量的资产绑定项。
- 选择人员(必须使用
selUser用户选择器)。 - 静态枚举或简短的数据字典选项(必须使用
selSysDic数据字典选择器)。 - 非运维类的纯物理商品卡片(必须使用 SPU 相关的
global-search-spu-drawer)。
3. 资源归属与引用方式
Views/Components/Common/selResource.cshtml@await Html.PartialAsync("~/Views/Components/Common/selResource.cshtml", new ViewDataDictionary(ViewData) { { "inputId", "MyResSelector" }, { "allowMultiple", true } })/api/star-pomelo/operations/tool-resource-page4. 参数配置说明 (Razor Parameters)
| 参数名 | 类型 | 默认值 | 详细功能与规范说明 |
|---|---|---|---|
inputId |
String | 必填 | 组件实例唯一 ID。将在页面生成 #{inputId}_json 隐藏域和 #{inputId}_triggerBtn 触发按钮。 |
allowMultiple |
Boolean | true |
是否开启多选绑定。true 为多选,false 为单选。 |
placeholder |
String | "请选择运维资源" |
输入框空白占位文案。 |
required |
Boolean | false |
是否启用 DataBinder 自动校验。开启后未选值提交会触发高亮警示。 |
5. 关键 DOM 约定与隐藏域机制
为支持 DataBinder 的无缝自动打包提交,selResource 遵循严格的隐藏域 DOM 架构规范:
- 核心隐藏域:
<input type="hidden" id="#{inputId}_json" name="#{inputId}_json" />,用于持久化勾选的完整 JSON 数组。 - 物理隐藏值域:
<input type="hidden" id="#{inputId}" name="#{inputId}" />,存放以逗号分隔的主键 ID 串(如"12,45,99"),供传统 MVC 表单提交。 - 可视化外壳:
<input type="text" id="#{inputId}_displayName" readonly class="form-control" />,用于展示已选中的资源名称串(如"WebSvr-01, Redis-Cluster-A")。
6. 回调方法与输出数据结构 (selectedItems)
点击确认绑定后,系统会自动触发全局或绑定的 callback 确认事件。入参 selectedItems 是个结构化的完整物理资源对象数组:
[
{
"id": "res_982",
"resourceName": "Nginx-Core-LB-01",
"innerIp": "192.168.12.105",
"resourceTypeCode": "NET_LOAD_BALANCER",
"resourceTypeText": "负载均衡服务",
"regionCode": "CN_HANGZHOU",
"status": "RUNNING",
"statusText": "活跃运行中"
}
]
7. 标准接入调用示例
<!-- 1. Razor 视图层引入组件 -->
<div class="form-group col-md-12">
<!-- 必须指定 inputId 和是否多选 (ViewDataDictionary 字典映射) -->
@await Html.PartialAsync("~/Views/Components/Common/selResource.cshtml", new ViewDataDictionary(ViewData) {
{ "inputId", "TargetServer" },
{ "allowMultiple", true },
{ "placeholder", "请选择目标运维集群机器" }
})
</div>
<script>
$(function() {
// 2. 页面内注册选择确认回调事件
window.TargetServer_callback = function(selectedItems) {
console.log("用户确认选中的资源数据: ", selectedItems);
// 可实现联动赋值,例如动态提取 IP 地址填入其他表单域
if (selectedItems.length > 0) {
var firstIp = selectedItems[0].innerIp;
$("#AssignedIpInput").val(firstIp);
}
};
// 3. 回显编辑数据 (通过 API 获取详情后手动回填输入框及触发联动)
Ajax.Get('/api/star-pomelo/operations/tool-resource-detail', { id: "res_982" }, function(res) {
// 回填主键值串
$("#TargetServer").val(res.id);
// 回填可视化展示名称 (selResource 会读取此 input 展现)
$("#TargetServer_displayName").val(res.resourceName);
// 回填完整的 JSON 数组字符串隐藏域 (供再次编辑提交)
$("#TargetServer_json").val(JSON.stringify([{ id: res.id, resourceName: res.resourceName, innerIp: res.innerIp }]));
});
});
</script>
8. 常见错误 Do & Don't
正确做法 (Do)
- 编辑回填时,除赋值主键
#TargetServer外,必须同时为#TargetServer_json赋值,否则界面无法渲染出回显文字。 - 如果是在 Modal 弹窗内套用
selResource,请务必保证 Modal 的 size 达到lg以上,以防 ag-Grid 没有足够的宽度产生列挤压。
错误做法 (Don't)
- 绝对禁止自己手写接口再去拼装 Bootstrap Modal 去选设备。统一采用
selResource.cshtml走标准管道。 - 禁止直接清空隐藏域而不重置
_displayName输入框,容易导致视觉数据残留、误导用户。
提交遮罩与确认交互行为
在表单提交或进行危险操作时,必须严格遵守以下控制协议,防止重复点击与低级数据破坏:
1. “保存中” 异步状态锁定
当用户点击 Modal 的“确定”保存表单时,必须立刻在按钮上追加 Loading 旋转图标并将按钮设为 disabled。严禁让按钮处于可再次点击的裸露状态。
// 典型保存按钮交互控制代码
$('#btnSaveDoc').click(function () {
var $btn = $(this);
// 1. 前置表单合法性校验
if (!$('#docForm').valid()) return;
// 2. 状态锁定,注入 Loading
$btn.prop('disabled', true).html('<i class="fal fa-spinner-third fa-spin mr-1"></i>保存中...');
Ajax.Post('/api/personnel/document-save', $('#docForm').serializeObject(), function (res) {
Msg.Success('提示', '单据已保存成功');
CommonModal.close(); // 自动关闭弹窗
}, function (err) {
// 失败后解除锁定,恢复状态
$btn.prop('disabled', false).html('<i class="fal fa-save mr-1"></i>保存');
Msg.Error('保存失败', err.message);
});
});
2. 强警示二次确认
禁止在未加阻断确认的情况下直接处理如“删除”、“废弃”、“作废审批”等不可逆流程。必须调用统一的 Msg.Confirm 样式对话框:
Msg.Confirm('作废单据', '作废后该单据将无法在报表中体现,且此操作不可逆。确认作废?', function() {
Ajax.Post('/api/finance/doc-void', { id: currentDocId }, function() {
Msg.Success('作废成功', '该单据已置为作废状态');
myGrid.refresh();
});
});