Acme Harmony UI Design System
Components / Container & Selectors

弹窗与抽屉 (Modals & Drawers)

定义了模态弹窗与右侧抽屉的选用判定边界、防双重滚动垂直溢出 Flex 布局规范,并重点补充了 selResource 运维资源选择组件的 API 标准、ag-Grid 底层交互细节及数据契约。

基础库: wwwroot/js/lib.js 选择组件: Views/Components/Common/selResource.cshtml 最后更新: 2026-05-28
01 / Selection Rules

弹窗 vs 抽屉 选用决策树

为避免滥用弹窗造成用户视野被强制打断,必须根据操作流程的“任务复杂度”和“上下文依赖度”进行精确选择:


容器类型 适用边界与场景 典型交互场景 核心设计理由与交互契约
模态弹窗
(Modal)
1. 短小、即时、低干扰的任务。
2. 上下文弱依赖:不需要参照底层表格。
3. 字段数量少于 10 个,且无复杂分区。
密码修改、单项修改(重命名)、警示理由录入、新建精简表单、危急确认。 强力遮罩,强制阻断,使用户高聚焦于极小范围内,快速点击“确认/取消”退出。支持按 ESC 键退出(表单未变更时)。
侧边抽屉
(Drawer)
1. 中长、深度编辑或配置类任务。
2. 强上下文依赖:需要一边看底层列表一边进行比对录入。
3. 包含大量标签页 (Tabs) 或包含分区折叠面板 (Collapse) 的大表单。
复杂详情查看、带有附表录入的申请单编辑、复杂审批流程记录、资源树授权配置。 从右侧平滑推入,保持左侧列表的视线,支持大幅垂直滚动排版,减少“新开详情页”的多标签割裂感。点击阴影背景默认不关闭。
02 / Sizing Tokens

尺寸与限宽规格

所有弹窗和抽屉必须统一映射全局宽度 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 极复杂系统。用于看板透视、内联大型表单或图形节点设计器。
03 / Layout Pattern

防双重滚动垂直布局规范

为消灭屏幕内出现“双重滚动条”以及“确定按钮被挤出屏幕外”的低级缺陷,弹窗和抽屉必须遵循高度视口截断、Header/Footer 固底、Body 内部垂直滚动的设计契约:


标准 Modal 骨架规范

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;          /* 自动撑满剩余高度 */
}
04 / Dedicated Selector

selResource 运维资源选择器

1. 组件定位与运行机制

selResource 是专为基础设施、运维节点及云服务等核心设备资产设计的高级筛选器。底层整合了 acmeAgGrid,采用弹窗表格分页加载模式(selectGrid),通过后端接口进行高速结构化字段检索,彻底杜绝了将数千条资源直接塞入下拉单选框导致的页面卡死灾难。

2. 何时使用 / 何时不要使用

何时使用 (Do)
  • 需要在大量物理机、容器集群、网络交换机、IP 地址等运维资源中进行选取。
  • 需要多列信息展示(如:资源名称、内网IP、区域、类型、运行状态)以防混淆。
  • 涉及多选绑定,或者单表单有大量的资产绑定项。
何时不用 (Don't)
  • 选择人员(必须使用 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-page

4. 参数配置说明 (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 输入框,容易导致视觉数据残留、误导用户。
05 / Save & Confirm Interaction

提交遮罩与确认交互行为

在表单提交或进行危险操作时,必须严格遵守以下控制协议,防止重复点击与低级数据破坏:


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();
    });
});
代码复制成功!已写入剪贴板