对话框与消息
文档:
vjcad/doc/ui/dialogs.md、vjcad/doc/ui/controls.md(message 部分) 示例:
vjcad/example/ui/02message.js— Toastvjcad/example/ui/04yesnodialog.js— confirm / info / warning / errorvjcad/example/ui/05inputdialog.js— input / select / promptvjcad/example/ui/10dialogsuspend.js— suspend / resumevjcad/example/ui/12modaldialogbase.js—ModalDialogBasevjcad/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— Toastvjcad/example/ui/04yesnodialog.js— confirm / info / warning / errorvjcad/example/ui/05inputdialog.js— input / select / promptvjcad/example/ui/10dialogsuspend.js— suspend / resumevjcad/example/ui/12modaldialogbase.js—ModalDialogBasevjcad/example/ui/13modelesspanelbase.js—ModelessPanelBase