唯杰地图 logo唯杰地图
VJMAP
EN

对话框与消息

vjcad/references/ui/dialogs.md 11.1 KB274 行
原文

文档:vjcad/doc/ui/dialogs.md、vjcad/doc/ui/controls.md(message 部分) 示例:

  • vjcad/example/ui/02message.js — Toast
  • vjcad/example/ui/04yesnodialog.js — confirm / info / warning / error
  • vjcad/example/ui/05inputdialog.js — input / select / prompt
  • vjcad/example/ui/10dialogsuspend.js — suspend / resume
  • vjcad/example/ui/12modaldialogbase.js — ModalDialogBase
  • vjcad/example/ui/13modelesspanelbase.js — ModelessPanelBase

1. Toast 消息(message)

message.info('操作成功');
message.success('保存成功');
message.warn('请注意');
message.error('操作失败');

// 配置式:duration=0 不自动关闭;同 key 会覆盖
message.info({ content: '正在保存…', duration: 0, key: 'saving' });
message.info({ content: '保存成功!',  duration: 3, key: 'saving' });

特性:

  • 顶部居中、最多同时 3 条、超出滑出
  • duration 单位秒(默认 3,0 表示常驻)
  • 多参数自动空格拼接:message.info('加载完成', 100, '条')

writeMessage('<br/>...') 是把内容写到底部命令行,不是 Toast。

2. 便捷对话框

// 输入
const r = await showInputDialog({
    title: '图层名称', label: '请输入新图层名',
    defaultValue: 'NewLayer', placeholder: '例如 Layer1',
    required: true,
    validator: v => v.length > 31 ? '不能超过31字符' : null
});
if (r.confirmed) console.log(r.value);

// 选择(下拉)
const s = await showSelectDialog({
    title: '选择线型', label: '请选择',
    options: [
        { value: 'Continuous', label: '实线' },
        { value: 'Hidden',     label: '虚线' }
    ],
    defaultValue: 'Continuous'
});

// 简易 prompt
const name = await showPrompt('请输入名称', '默认值', '标题');

// 确认 / 警告 / 信息 / 错误
const ok       = await showConfirm('确定删除?', '删除确认');
const proceed  = await showWarningConfirm('不可撤销,继续?', '警告');
await showInfo('操作完成');
await showError('保存失败:权限不足');

返回:showInputDialog → InputDialogResult = { confirmed: boolean, value?: string }(value 已 trim());showPrompt → string | null(取消返回 null;值已 trim,且内部 required: true,空输入不能确认);确认类返回 boolean。

3. YesNoDialogConfig 完整字段

class YesNoDialogConfig {
    title       = 'WebCAD';
    message     = '';                                 // 支持 HTML
    type        = 'confirm';                          // 'info' | 'warning' | 'error' | 'confirm'
    yesTitle    = '是(Y)';
    noTitle     = '否(N)';
    showCancel  = false;
    cancelTitle = '取消';
    confirmOnly = false;                              // 只显示确定按钮
}

InputDialogConfig / SelectDialogConfig 见 vjcad/doc/ui/dialogs.md 完整字段定义。

4. 对话框中拾取点 / 选择实体(suspend / resume)

BaseDialogComponent 暴露 suspend() / resume()。必须在命令上下文中弹出对话框 才能让 CAD 接管交互。

class InteractiveCommand {
    async main() {
        const dialog = new MyInteractiveDialog();
        const result = await dialog.startDialog();   // 见下方组件
        if (result) console.log(result);
    }
}
CommandRegistry.regist(new CommandDefinition('MYINTER', '交互', InteractiveCommand, new CommandOptions()));
await Engine.editor.executerWithOp('MYINTER');

组件中:

async pickPoint() {
    this.baseDialog?.suspend();
    const opt = new PointInputOptions('指定点:');
    const r   = await Engine.editor.getPoint(opt);
    this.baseDialog?.resume();
    if (r.status === InputStatusEnum.OK) this.point = r.value;
}

async selectEntities() {
    this.baseDialog?.suspend();
    const r = await Engine.editor.getSelections(new SelectionInputOptions());
    this.baseDialog?.resume();
    if (r.status === InputStatusEnum.OK && r.value?.length) this.entities = r.value;
}

完整组件:vjcad/example/ui/10dialogsuspend.js,Vue3 版:vjcad/example/ui/11dialogsuspend-vue3.js

5. ModalDialogBase(模态对话框基类)

class MySettingsDialog extends ModalDialogBase {
    static dialogTitle = '设置';
    static properties  = { ...ModalDialogBase.properties, color: { type: Number }, width: { type: Number } };
    color = 1; width = 1;

    renderContent() {
        return html`
            <div class="row"><span class="label">颜色:</span>
                <input type="number" class="input" .value=${String(this.color)}
                       @input=${e => this.color = parseInt(e.target.value)}></div>
            <div class="row"><span class="label">线宽:</span>
                <input type="number" class="input" .value=${String(this.width)}
                       @input=${e => this.width = parseInt(e.target.value)}></div>`;
    }

    confirm() {                       // 用户点确定
        this.result = { color: this.color, width: this.width };
        this.close();
    }
}
customElements.define('my-settings-dialog', MySettingsDialog);

const dialog  = new MySettingsDialog();
const result  = await dialog.startDialog();          // 等待用户操作
if (result) console.log(result.color, result.width);

API(节选):

方法 / 属性 说明
static dialogTitle 标题
result 你在 confirm() 里赋值的返回值
useShadowDOM 默认 true;用第三方库时设 false
startDialog(options?) 显示并 await 结果
renderContent() 必须实现
renderFooter() 自定义底栏(默认确定/取消)
confirm() / cancel() / close() 标准回调
suspend() / resume() / isSuspended 与第 4 节一致

6. ModelessPanelBase(非模态面板基类)

class DrawToolsPanel extends ModelessPanelBase {
    static panelTitle      = '绘图工具';
    static panelWidth      = '200px';
    static initialPosition = { top: '100px', right: '20px' };

    drawLine() {
        const ln = new LineEnt([0, 0], [100, 50]);
        ln.setDefaults();
        Engine.addEntities(ln);
        Engine.zoomExtents();
    }

    renderContent() {
        return html`
            <button class="btn btn-primary" @click=${this.drawLine}>画直线</button>
            <button class="btn"             @click=${() => Engine.zoomExtents()}>缩放全图</button>`;
    }
}
customElements.define('draw-tools-panel', DrawToolsPanel);

const panel = new DrawToolsPanel();
document.body.appendChild(panel);
panel.show();      // panel.hide() / toggle() / destroy()

工厂:createPanel(PanelClass, tagName):tagName 未注册时自动 customElements.define,然后每次调用都 createElement 一个新面板并 append 到 document.body,返回管理器(show / hide / toggle / destroy / isVisible)。它不是单例——要单例就把返回的管理器缓存在模块变量里,只调一次 createPanel。

7. 自定义 BaseDialogComponent

ModalDialogBase 已封装了 80% 场景;若要彻底自定义底栏、布局,可直接继承 BaseDialogComponent。骨架:

// BaseDialogComponent 本身就是 <base-dialog> 元素类;子类必须先注册成自定义元素,否则 new 会抛 Illegal constructor
class MyCustomDialog extends BaseDialogComponent {
    result: any = undefined;

    async show() {
        Engine.dialog.appendChild(this);
        await this.waitUpdated();
        await this.shadowRoot.querySelector('base-dialog')._startBaseDialog({
            title: '我的对话框', renderTarget: this.renderRoot
        });
        this.remove();
        return this.result;
    }

    render() {
        return html`<base-dialog>
            <div id="container">
                <input .value=${this.value} @input=${e => this.value = e.target.value}/>
                <div id="button-bar">
                    <dlg-button accept="true" @click=${this.onConfirm}>确定</dlg-button>
                    <dlg-button             @click=${this.onCancel}>取消</dlg-button>
                </div>
            </div>
        </base-dialog>`;
    }
}
if (!customElements.get('my-custom-dialog')) customElements.define('my-custom-dialog', MyCustomDialog);

const r = await new MyCustomDialog().show();

<dlg-button accept="true"> 是主按钮;disabled 表示禁用。

8. 主题 CSS 变量

<base-dialog> 在 :host 上自带 --dialog-header-bg / --dialog-header-color / --dialog-header-border / --dialog-contents-color / --dialog-ele-border-color 默认值,而且每个对话框的 <base-dialog> 都在该对话框自己的 shadow root 里——写在页面全局 CSS 里的 base-dialog {...} 选不中它,不会生效。

  • 只改自己的对话框:把规则写进自己组件的 static styles(同一 shadow root 内的 base-dialog 选择器优先于它的 :host 默认值)。ModalDialogBase / ModelessPanelBase 已内置 :host, base-dialog { --dialog-header-bg: var(--theme-color-dialog-header-bg, …) } 这类映射:
class MyDialog extends ModalDialogBase {
    static styles = [...ModalDialogBase.styles, css`
        base-dialog { --dialog-header-bg: #2d2d30; --dialog-header-color: #f0f0f0; }
    `];
}
  • 整体换色:cadView.setThemeColors({ dialogHeaderBg, dialogBg, dialogBorder, dialogFont, dialogInputBg, font, ... })(或 Engine.setThemeColors),写的是 document.documentElement 上的 --theme-color-dialog-* 等变量;基于 ModalDialogBase / ModelessPanelBase(样式里做了 --dialog-* → --theme-color-dialog-* 映射)的对话框会跟随,而颜色 / 线型等老式 picker 的 <base-dialog> 用固定默认色,不受影响。

9. 快捷键

键 作用
Enter 确认 / 是
Escape 取消 / 否
Y / N YesNoDialog 是 / 否

红旗

  • suspend / resume 必须 配对,且 在命令里调用对话框,否则鼠标点画布会触发默认命令
  • ModalDialogBase.confirm() 默认会自动 close();如果你在 confirm() 里继续异步处理,记得校验失败时 不要调 close()
  • useShadowDOM = false 后样式不再隔离,要手动注入 createNoShadowStyles(tag)
  • showInputDialog 的 validator 是同步函数,不能 await;做异步校验请自己写组件
  • 多次 panel.show() 会触发 onShow 多次;createPanel(...) 每调一次就新建一个面板实例(不是单例),想保持单例请自己缓存返回的管理器

参考(官方文档 MCP)

  • 文档:vjcad/doc/ui/dialogs.md、vjcad/doc/ui/controls.md(message 部分)
  • 示例:
  • vjcad/example/ui/02message.js — Toast
  • vjcad/example/ui/04yesnodialog.js — confirm / info / warning / error
  • vjcad/example/ui/05inputdialog.js — input / select / prompt
  • vjcad/example/ui/10dialogsuspend.js — suspend / resume
  • vjcad/example/ui/12modaldialogbase.js — ModalDialogBase
  • vjcad/example/ui/13modelesspanelbase.js — ModelessPanelBase