唯杰地图 logo唯杰地图
VJMAP
EN

基础控件(NumberInput / ToolBarButton / 线型预览)

vjcad/references/ui/controls.md 9.9 KB203 行
原文

文档:vjcad/doc/ui/controls.md 示例:

  • vjcad/example/ui/12modaldialogbase.js — 在对话框中使用 NumberInput
  • vjcad/example/ui/13modelesspanelbase.js — 工具栏 / 自定义按钮
  • vjcad/example/ui/04yesnodialog.js — 对话框基础

vjcad 把"和 CAD 习惯一致的小控件"做成 Web Components,可在自定义对话框 / 面板中复用。

1. NumberInputComponent (<input-number>)

<input-number
    .value=${this.x}
    .luprec=${4}             precision: 小数位
    .allowMinus=${true}      允许负数
    .allowZero=${true}       允许 0
    @change=${this.onXChange}>
</input-number>

属性:

属性 类型 说明
value number 仅初始值:只在 connectedCallback 时转成显示文本,之后再改 .value 不会刷新显示,也不同步 numValue
luprec number 显示精度(小数位,默认 6)
allowMinus boolean 是否允许负数(默认 false)
allowZero boolean 是否允许 0(默认 false;坐标类输入两个都要开)
disabled boolean
theme string 'dark' | 'light'

⚠️ 组件没有 min / max / step / placeholder 属性——范围校验只能自己在 change 里做。 另有 isAny / numValue / numStr / ANYSTR 内部态和 setValue/updateValue/setNumbers/setDisabled 方法。 程序化改值用 updateValue(v)(setValue 只改内部态不刷新输入框);读当前数值用 numValue(初始为 0,value 不会写进它,直到用户提交或 setValue/updateValue)。

事件:

host.addEventListener('change', e => {
    const v = e.detail.value;       // ⚠️ string(输入原文),数值化用 Number(v) 或读 e.target.numValue
});
// 每次 Enter、每次失焦(值合法时)都会触发——按 Enter 后再点别处会收到第二次 change,处理要幂等
// 没有向外抛 'input' 事件——input_callback 只做内部校验

实战:在属性面板里编辑端点:

class PointEditor extends LitElement {
    @property({ type: Object }) point = { x: 0, y: 0 };

    onXChange(e) { this.point = { ...this.point, x: Number(e.detail.value) }; this._emit(); }
    onYChange(e) { this.point = { ...this.point, y: Number(e.detail.value) }; this._emit(); }
    _emit() { this.dispatchEvent(new CustomEvent('change', { detail: this.point })); }

    render() {
        return html`
            <div>X: <input-number .value=${this.point.x} .luprec=${4} .allowMinus=${true} .allowZero=${true}
                                  @change=${this.onXChange}></input-number></div>
            <div>Y: <input-number .value=${this.point.y} .luprec=${4} .allowMinus=${true} .allowZero=${true}
                                  @change=${this.onYChange}></input-number></div>`;
        // 外部改了 point 想刷新显示:this.renderRoot.querySelector('input-number').updateValue(newX)
    }
}

2. ToolbarButton (<tool-bar-button>)

<tool-bar-button
    title="直线"
    src="images/commands/line.svg"
    @click=${() => Engine.editor.executerWithOp('LINE')}>
</tool-bar-button>

属性(实现类 ButtonEle):

属性 说明
title tooltip + a11y label
execute 命令名字符串而已:按钮自身不绑定点击,只有内置工具条(如命令行工具区)读取它;自己用要 @click 里 executerWithOp
src 不是任意 URL:只认 [./]images/<分类>/<名>.svg 形式,从中解析出分类(commands/actbar/func-buttons/…)和名字去 IconRegistry 查;不匹配则不显示图标(不会发请求加载图片)
svgContent 直接给 SVG 字符串(优先于 src);自定义图标最稳的写法
theme 'dark' | 'light'
isOn 声明了但 ButtonEle 渲染里没用到——无任何视觉效果
state 状态字符串(配合 preSelectMode 用)
preSelectMode 预选模式标记

⚠️ 没有 disabled / selected 属性;isOn 也不产生高亮——按下/选中态请自己用外层 class 或 svgContent 换图标表现。

事件:

btn.addEventListener('click', () => Engine.editor.executerWithOp('LINE'));   // execute 属性不会自动执行
// 没有 'select' 事件——按钮态切换自己处理

实战:自定义浮动工具栏(无需 createFloatingToolbar 的简化版):

class MyToolbar extends LitElement {
    render() {
        return html`
            <div style="display:flex; gap:4px; padding:4px;">
                <tool-bar-button title="直线" src="images/commands/line.svg"
                    @click=${() => Engine.editor.executerWithOp('LINE')}></tool-bar-button>
                <tool-bar-button title="圆"   src="images/commands/circle.svg"
                    @click=${() => Engine.editor.executerWithOp('CIRCLE')}></tool-bar-button>
                <tool-bar-button title="弧"   src="images/commands/arc.svg"
                    @click=${() => Engine.editor.executerWithOp('ARC')}></tool-bar-button>
            </div>`;
    }
}
customElements.define('my-toolbar', MyToolbar);

3. 线型预览(没有独立 <linetype-preview> 元素)

vjcad 没有导出独立的线型预览控件。可用的真实路径:

  • LinetypeViewerDialog(<linetype-viewer-dialog>):只读对话框,内部 loadLinetypeContent() 预览全部已加载线型
  • layer-linetype-cell:图层管理表里渲染"线型"列的内部单元格组件(LayerLinetypeCell,name 属性),绑定在图层表格流程里,不适合单独复用
  • 自定义下拉里的预览,用 Engine.linetypeManager.getLinetype(name) 拿 LinetypeDefinition(elements/patternLength)自己画 <canvas>,十几行即可
// 只读查看全部已加载线型:
const viewer = new LinetypeViewerDialog();
await viewer.startDialog();

// 自己画缩略:取线型定义(注意是 Engine.linetypeManager,不是 currentDoc.lineTypes)
const lt = Engine.linetypeManager.getLinetype('Hidden');   // LinetypeDefinition | null
console.log(lt.elements, lt.patternLength);

4. 启动撤销标记

vjcad 在自定义按钮 / 面板中改 model 时,要把"一次操作"打成 undo 单元:

Engine.startUndoMark('修改坐标');
selected.forEach(e => {
    Engine.markModified(e);          // = undoManager.modEntity_undoMark(e):先记录修改前状态
    e.startPoint = newPoint;         // 再改
});
Engine.endUndoMark();
Engine.regen();

startUndoMark/endUndoMark 是 Engine 上的 static(也作为顶层函数导出),参数收 string | UndoDescriptor;Engine.undoManager(UndoManager)上的同名功能内部叫 start_undoMark,不要直接调。它们只负责把中间的撤销记录合成一组,本身不记录任何修改——直接赋值属性不会进撤销栈,必须在改之前逐个 Engine.markModified(e)(或 Engine.markModifiedBatch(arr))。

5. message / writeMessage 速记

import { message, writeMessage } from 'vjcad';

message.info({ content: '保存中…', duration: 0, key: 'save' });
await save();
message.success({ content: '保存成功', duration: 3, key: 'save' });

writeMessage('<br/>已添加 ' + n + ' 个实体');

详见 dialogs 第 1 节。

6. 主题相关 CSS 变量

没有 --input-bg / --button-bg 这类变量。控件直接读全局主题变量(THEME_COLOR_CSS_VARS 里的 --theme-color-*,组件内部不重定义,所以在祖先元素上设置即可继承):

控件 读取的变量
<input-number> --theme-color-input-bg(底色)、--theme-color-font(字色)、--theme-color-border(边框)、--theme-color-accent(valid 态边框)
<tool-bar-button> --theme-color-toolbar-button-bg(回退 --theme-color-input-bg)、--theme-color-border、--theme-hover(悬停)、--pre-select-mode(预选态边框)、light 主题用 --theme-color-font 作底
// 推荐:全局改(写到 document.documentElement,内置 UI 一起变)
Engine.setThemeColors({ inputBg: '#1e1e1e', font: '#f0f0f0', border: '#3d3d3d',
                        accent: '#4589ee', toolbarButtonBg: '#3a3a3a', hover: '#4a4a4a' });
/* 只影响自己面板里的控件:在自己组件的 static styles 中给宿主设变量 */
:host { --theme-color-input-bg: #1e1e1e; --theme-color-toolbar-button-bg: #3a3a3a; }

红旗

  • <input-number> 的 change 在每次 Enter、每次失焦(值合法时)都触发,Enter 后再失焦会触发两次;e.detail.value 是字符串原文——用 Number(e.detail.value) 取值;输入中途没有 input 事件可监听;.value 只是初始值,程序化改值用 updateValue(v);默认 allowZero/allowMinus 都是 false
  • <tool-bar-button> 的 execute 不会自动执行、isOn 没有视觉效果、src 只认 images/<分类>/<名>.svg 注册表路径——点击自己绑 @click,自定义图标用 svgContent(没有 disabled/selected 属性)
  • 没有独立 <linetype-preview> 元素——预览线型用 LinetypeViewerDialog 或自己画
  • vjcad 控件都基于 Lit;自定义对话框 useShadowDOM=false 时这些控件依旧用 ShadowDOM,外层 CSS 不能穿透
  • 异步操作期间记得 message.key 复用同一 toast,避免叠出多条

参考(官方文档 MCP)

  • 文档:vjcad/doc/ui/controls.md
  • 示例:
  • vjcad/example/ui/12modaldialogbase.js — 在对话框中使用 NumberInput
  • vjcad/example/ui/13modelesspanelbase.js — 工具栏 / 自定义按钮
  • vjcad/example/ui/04yesnodialog.js — 对话框基础