bricks/bricks/widget.js
yumoqing 458b599061 feat(Text): 动态文本国际化 i18n_params 透传
TextBase.set_attrs/set_otext 把 opts.i18n_params 传给 i18n._(otext, params),
支持 otext 用占位符由后端传值(切语言重渲染同样带参)。

此前 i18n._ 已支持 obj 参数, 但 widget 层未透传 -> 后端声明 i18n_params 的
控件渲染出字面占位串。商机产线"数据参考"弹窗实测: 全部主题显示为原始占位符
而非条数/预算数字。

bundle(dist/bricks.js) 被 gitignore, 由部署侧 build.sh 构建。
2026-09-14 19:18:15 +08:00

654 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var bricks = window.bricks || {};
bricks.JsWidget = class {
/*
popup:{
popup_event:
popup_desc:
popupwindow:false or true
}
bgimage:url
*/
constructor(options){
if (!options){
options = {}
}
this.dom_element = null;
this.baseURI = options.baseURI;
this.opts = options;
this.create();
this.opts_set_style();
this._container = false;
this.parent = null;
this.sizable_elements = [];
this.set_id(bricks.uuid());
if (options.css){
this.set_css(options.css);
}
if (options.csses){
this.set_csses(options.csses);
}
this.dom_element.bricks_widget = this;
if (this.opts.tip){
var w = bricks.app.tooltip;
this.bind('mousemove', w.show.bind(w, bricks.app.i18n._(this.opts.tip||'')));
this.bind('mouseout', w.hide.bind(w));
this.bind('click', w.hide.bind(w));
}
if (this.popup){
this.bind(this.popup.popup_event, this.popup_action.bind(this));
}
if (this.bgimage){
this.set_bg_image(this.bgimage);
}
// 声明式端适配2026-09-07
// mobile_hidden: true — 手机视口隐藏(桌面显示)
// desktop_hidden: true — 桌面视口隐藏(手机显示)
// mobile_opts: {margin:'0 8px 8px', padding:'4px', ...} — 手机视口下
// 覆盖对应行内样式键,桌面恢复 options 原值(无则清空行内)
// 由 bricks/mobile.js 的 on_mobile_mode 统一驱动,控件自身无脚本。
// 注意Button 等子类构造在 super() 之后还会写行内 displayflex
// 会覆盖此处同步设置的 none —— 故首次应用推迟到构造链结束后
// schedule_once模式切换时的 _notify 仍同步应用(构造早已完成)。
if (options.mobile_hidden || options.desktop_hidden || options.mobile_opts){
schedule_once(this._apply_visibility_mobile.bind(this), 0);
if (!bricks._mobile_adapt_widgets) bricks._mobile_adapt_widgets = [];
bricks._mobile_adapt_widgets.push(this);
}
bricks.resize_observer.observe(this.dom_element);
}
_apply_visibility_mobile(){
var m = bricks.is_mobile ? bricks.is_mobile() : false;
var hide = (m && this.opts.mobile_hidden) || (!m && this.opts.desktop_hidden);
this.dom_element.style.display = hide ? 'none' : '';
var mo = this.opts.mobile_opts;
if (mo){
for (var k in mo){
var v = m ? mo[k] : (this.opts[k] !== undefined ? this.opts[k] : '');
this.set_style(k, v);
}
}
}
set_bg_image(url){
var d = this.dom_element;
d.style.backgroundImage = "url('" +url + "')";
d.style.backgroundSize = "cover"; // 背景图4填满容器
d.style.backgroundPosition = "center"; // 居中显示
d.style.backgroundRepeat = "no-repeat"; // 不重复
}
destroy_popup(){
this.popup_widget.destroy();
this.popup_widget = null;
}
async popup_action(){
if (this.popup_widget){
this.popup_widget.destroy();
this.popup_widget = null;
} else {
if (this.popup.popupwindow){
this.popup_widget = new bricks.PopupWindow(this.popup.optiosn);
} else {
this.popup_widget = new bricks.Popup(this.popup.options);
}
this.popup_widget.bind('dismissed', this.destroy_popup.bind(this));
var w = await bricks.widgetBuild(this.popup.popup_desc, this, this.user_data);
if (w){
this.popup_widget.add_widget(w);
this.popup_widget.open();
this.popup_widget.popup_from_widget(this);
}
}
}
showRectage(){
return this.dom_element.getBoundingClientRect();
}
is_in_dom(){
return document.contains(this.dom_element);
}
getUserData(){
return this.user_data || null;
}
setUserData(v){
this.user_data = v;
}
create(){
this.dom_element = this._create('div')
}
_create(tagname){
return document.createElement(tagname);
}
observable(name, value){
return new bricks.Observable(this, name, value);
}
is_disabled(){
return this.dom_element.disabled == true;
}
disabled(flag){
if(flag){
this.dom_element.disabled = true;
this.set_style('pointerEvents', 'none');
} else {
this.dom_element.disabled = false;
this.set_style('pointerEvents', 'auto');
}
}
opts_set_style(){
var keys = [
"width",
"dynsize",
"x",
"y",
"height",
"cursor",
"margin",
"marginLeft",
"marginRight",
"marginTop",
"marginBottom",
"padding",
"paddingLeft",
"paddingRight",
"paddingTop",
"paddingBottom",
"align",
"textAlign",
"overflowY",
"overflowX",
"overflow",
"flexShrink",
"flexGrow",
"flex",
"minWidth",
"maxWidth",
"minHeight",
"maxHeight",
"marginLeft",
"marginRight",
"marginTop",
"marginBottom",
"zIndex",
"overflowX",
"overflowY",
"color",
"borderRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
"border",
"borderTop",
"borderBottom",
"borderLeft",
"borderRight",
"borderColor",
"borderWidth",
"borderStyle",
"opacity",
"boxShadow",
"textShadow",
"textDecoration",
"textTransform",
"whiteSpace",
"wordBreak",
"wordWrap",
"overflowWrap",
"gap",
"alignItems",
"alignSelf",
"justifyContent",
"flexDirection",
"flexWrap",
"flexBasis",
"order",
"transition",
"transform",
"pointerEvents",
"userSelect",
"listStyle",
"objectFit",
"objectPosition",
"outline",
"boxSizing",
"display"
];
var mapping_keys = {
"bgcolor":"backgroundColor"
};
var mkeys = Object.keys(mapping_keys);
var style = {};
var okeys = Object.keys(this.opts);
for (var k=0; k<okeys.length; k++){
if (keys.find( i => i ==okeys[k])){
style[okeys[k]] = this.opts[okeys[k]];
}
if (mkeys.find( i => i ==okeys[k])){
var mk = mapping_keys[okeys[k]];
style[mk] = this.opts[okeys[k]];
}
this[okeys[k]] = this.opts[okeys[k]];
}
if (this.opts.cwidth){
this.width = bricks.app.charsize * this.opts.cwidth;
style.width = this.width + 'px';
}
if (this.opts.cheight){
this.height = bricks.app.charsize * this.opts.cheight;
style.height = this.height + 'px';
}
if (this.opts.dynsize){
bricks.app.bind('charsize', this.charsize_sizing.bind(this))
}
bricks.extend(this.dom_element.style, style);
if (this.opts.css){
this.set_css(this.opts.css);
}
}
charsize_sizing(){
var cs = bricks.app.charsize;
var r = this.rate || 1;
if (this.cwidth){
this.set_style('width', this.cwidth * cs * r + 'px');
}
if (this.cheight){
this.set_style('height', this.cheight * cs * r + 'px');
}
if (this.cfontsize){
this.dom_element.style.fontSize = this.cfontsize * cs * r + 'px';
if (this.sizable_elements){
for (var i=0;i<this.sizable_elements.length;i++){
var e = this.sizable_elements[i];
e.style.fontSize = this.cfontsize * cs * r + 'px';
}
}
}
}
enter_fullscreen(){
var e = this.dom_element;
// 获取要全屏显示的元素
// 检查浏览器是否支持Fullscreen API
if (e.requestFullscreen) {
e.requestFullscreen();
} else if (e.mozRequestFullScreen) { // Firefox
e.mozRequestFullScreen();
} else if (e.webkitRequestFullscreen) { // Chrome, Safari and Opera
e.webkitRequestFullscreen();
} else if (e.msRequestFullscreen) { // IE/Edge
e.msRequestFullscreen();
}
}
exit_fullscreen(){
document.exitFullscreen();
}
h_center(){
this.dom_element.style.marginLeft = 'auto';
this.dom_element.style.marginRight = 'auto';
}
h_left(){
this.dom_element.style.marginLeft = '0px';
this.dom_element.style.marginRight = 'auto';
}
h_right(){
this.dom_element.style.marginLeft = 'auto';
this.dom_element.style.marginRight = '0px';
}
ht_center(){
this.dom_element.style.textAlign = 'center';
}
ht_left(){
this.dom_element.style.textAlign = 'left';
}
ht_right(){
this.dom_element.style.textAlign = 'right';
}
set_width(width){
if (typeof(width) == 'number'){
width = width + 'px';
}
this.dom_element.style.width = width;
}
set_height(height){
if (typeof(height) == 'number'){
height = height + 'px';
}
this.dom_element.style.height = height;
}
set_style(k, v){
this.dom_element.style[k] = v;
}
set_csses(csses, remove_flg){
this.set_css(csses, remove_flg);
}
unset_css(css){
this.dom_element.classList.remove(css);
}
set_css(css, remove_flg){
var arr = css.split(' ');
arr.forEach(c => {
if (!remove_flg){
this.dom_element.classList.add(c);
} else {
this.dom_element.classList.remove(c);
}
});
}
set_cssObject(cssobj){
bricks.extend(this.dom_element.style, cssobj);
}
is_container(){
return this._container;
}
set_id(id){
this.id = id;
this.dom_element.id = id;
}
set_baseURI(uri){
this.baseURI = uri;
if (!this._container){
return;
}
this.children.forEach(c =>{
if (!c.baseURI){
c.set_baseURI(uri);
}
});
}
show(){
this.dom_element.style.display = '';
}
hide(){
this.dom_element.style.display = 'none'
}
is_hide(){
return this.dom_element.style.display == 'none';
}
toggle_hide(){
if (this.dom_element.style.display == 'none'){
this.show();
} else {
this.hide();
}
}
get_width(){
return this.dom_element.clientWidth;
}
get_height(){
return this.dom_element.clientHeight;
}
bind(eventname, handler){
this.dom_element.addEventListener(eventname, handler);
}
unbind(eventname, handler){
this.dom_element.removeEventListener(eventname, handler);
}
dispatch(eventname, params){
if (typeof params === "string" || params instanceof String) {
console.log('event name=', eventname, 'params is string =', params, this);
}
var e = new Event(eventname);
e.params = params;
this.dom_element.dispatchEvent(e);
}
set_attribute(attr, value){
this.dom_element.setAttribute(attr, value);
}
get_attribute(attr) {
this.dom_element.getAttribute(attr);
}
selected(flag){
if(flag){
this.set_css('selected');
} else {
this.set_css('selected', true);
}
}
}
bricks.TextBase = class extends bricks.JsWidget {
/* {
otext:
i18n:
rate:
halign:
valign:
css
}
*/
constructor(options){
options.halign = options.halign || 'center';
options.valign = options.valign || 'center';
super(options);
this.opts = options;
this.rate = this.opts.rate || 1;
this.specified_fontsize = false;
this.set_attrs();
this.dom_element.style.fontWeight = 'normal';
this.set_style('display', 'flex');
switch (options.halign) {
case 'left':
this.set_style('justifyContent', 'flex-start');
break;
case 'right':
this.set_style('justifyContent', 'flex-end');
break;
default:
this.set_style('justifyContent', 'center');
break;
}
switch (options.valign){
case 'top':
this.set_style('alignItems', 'flex-start');
break;
case 'bottom':
this.set_style('alignItems', 'flex-end');
break;
default:
this.set_style('alignItems', 'center');
break;
}
if (options.wrap){
this.set_style('flexWrap', 'wrap');
}
if (this.i18n){
bricks.app.bind('lang', this.set_i18n_text.bind(this));
}
}
set_attrs(){
if (this.opts.hasOwnProperty('text')){
this.text = this.opts.text;
} else if (this.opts.hasOwnProperty('label')){
// 兼容 label 写法LLM 生成 .ui 常用 label此前静默渲染空文本
this.text = this.opts.label;
}
if (this.opts.hasOwnProperty('otext')){
this.otext = this.opts.otext;
}
if (this.opts.hasOwnProperty('i18n')){
this.i18n = this.opts.i18n;
}
if (this.opts.hasOwnProperty('i18n_params')){
// 动态文本国际化otext 用 ${name} 占位i18n_params 提供替换值
// 切语言时 set_i18n_text→set_otext 会带 i18n_params 重新 obj_fmtstr
this.i18n_params = this.opts.i18n_params;
}
this.set_style('flexShrink', '0');
if (this.i18n && this.otext) {
this.text = bricks.app.i18n._(this.otext, this.i18n_params);
}
this.dom_element.innerHTML = this.text || '';
}
set_otext(otxt){
var text;
this.otext = otxt || '';
if (this.i18n) {
text = bricks.app.i18n._(this.otext, this.i18n_params);
} else {
text = this.otext;
}
this.set_text(text);
}
set_i18n_text(){
if ( !this.otext){
return;
}
if (! this.i18n){
return;
}
this.set_otext(this.otext);
}
set_text(text){
this.text = text;
this.dom_element.innerHTML = this.text || '';
}
}
bricks.Text = class extends bricks.TextBase {
constructor(opts){
super(opts);
this.cfontsize = 1;
this.charsize_sizing();
}
}
bricks.KeyinText = class extends bricks.Text {
constructor(opts){
super(opts);
if (! this.name) {
this.name = 'data';
}
bricks.app.bind('keydown', this.key_down_action.bind(this))
}
key_down_action(event){
if (! event.key) return;
switch (event.key) {
case 'Delete':
this.set_text('');
this.dispatch_changed();
break;
case 'Backspace':
var s = this.text.substring(0, this.text.length - 1);
this.set_text(s);
this.dispatch_changed();
break;
default:
if (event.key.length == 1){
var txt = this.text + event.key;
this.set_text(txt);
this.dispatch_changed();
}
break;
}
}
dispatch_changed(){
var d = {};
d[this.name] = this.text;
this.dispatch('changed', d);
}
}
bricks.Title1 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title1';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.96;
this.charsize_sizing();
}
}
bricks.Title2 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title2';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.80;
this.charsize_sizing();
}
}
bricks.Title3 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title3';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.64;
this.charsize_sizing();
}
}
bricks.Title4 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title4';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.48;
this.charsize_sizing();
}
}
bricks.Title5 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title5';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.32;
this.charsize_sizing();
}
}
bricks.Title6 = class extends bricks.TextBase {
constructor(options){
super(options);
this.ctype = 'title6';
this.dom_element.style.fontWeight = 'bold';
this.cfontsize = 1.16;
this.charsize_sizing();
}
}
bricks.Tooltip = class extends bricks.Text {
constructor(opts){
opts.rate = 0.8;
opts.tip = null;
super(opts);
this.set_css('modal');
this.set_style('minWidth', '90px');
this.auto_task = null;
}
show(otext, event){
this.set_otext(otext);
this.set_style('zIndex', 999999999);
this.set_style('display', 'block');
bricks.relocate_by_eventpos(event, this);
if (this.auto_task){
clearTimeout(this.auto_task);
this.auto_task = null;
}
this.auto_task = schedule_once(this.hide.bind(this), 6);
}
hide(){
try {
if (this.auto_task){
clearTimeout(this.auto_task);
this.auto_task = null;
}
} catch(e){
console.log('Exception:', e);
}
this.set_style('display', 'none');
}
}
bricks.Factory.register('Tooltip', bricks.Tooltip);
bricks.Factory.register('Text', bricks.Text);
bricks.Factory.register('KeyinText', bricks.KeyinText);
bricks.Factory.register('Title1', bricks.Title1);
bricks.Factory.register('Title2', bricks.Title2);
bricks.Factory.register('Title3', bricks.Title3);
bricks.Factory.register('Title4', bricks.Title4);
bricks.Factory.register('Title5', bricks.Title5);
bricks.Factory.register('Title6', bricks.Title6);