bricks/bricks/tab.js

320 lines
11 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.TabPanel = class extends bricks.Layout {
/*
options
{
css:
tab_long: 100%
tab_pos:"top"
items:[
{
name:
label:"tab1",
icon:
removable:
refresh:false,
content:{
"widgettype":...
}
}
]
}
css:
tab
tab-button
tab-button-active
tab-button-hover
tab-content
events:
switch: fired when switch content
event data: actived content widget
content wedget event:
active: fired when the content event is switch to show
*/
constructor(options){
super(options);
this.content_buffer = {};
this.cur_tab_name = '';
this.content_container = new bricks.Filler({});
if (this.opts.tab_pos == 'top' || this.opts.tab_pos == 'bottom'){
this.set_css('vbox');
this.tab_container = new bricks.VBox({
height:'auto',
width:'auto'
});
} else {
this.set_css('hbox');
this.tab_container = new bricks.VBox({
width:'auto',
height:'auto'
});
}
if (this.opts.tab_pos == 'top' || this.opts.tab_pos == 'left'){
this.add_widget(this.tab_container);
this.add_widget(this.content_container);
} else {
this.add_widget(this.content_container);
this.add_widget(this.tab_container);
}
this.createToolbar();
this.set_css('tabpanel');
this.content_container.set_css('tabpanel-content');
// 移动端底部标签栏模式2026-09-07options.mobile_bar = {items:[{name,label,micon,url}]}
// 手机视口隐藏顶部 tab 条,改用固定底部 TabBar点击 → open_tab框架原生动态 tab
// 桌面视口 TabBar 隐藏,行为与原来完全一致(侧边栏菜单驱动 open_tab
if (this.opts.mobile_bar){
this.createMobileBar();
}
this.show_first_tab();
}
createMobileBar(){
var mb = this.opts.mobile_bar;
var items = ((mb && mb.items) || []).map(function(it){
return {name: it.name, label: it.label || it.name, icon: it.micon || '',
badge_url: it.badge_url || ''};
});
this.mobile_bar = new bricks.TabBar({items: items, badge_interval: mb.badge_interval || 60});
this._mobile_bar_descs = (mb && mb.items) || [];
this.mobile_bar.bind('command', this._mobile_bar_clicked.bind(this));
bricks.Body.add_widget(this.mobile_bar);
var self = this;
this._mobile_bar_apply = function(m){
var show = m && !!self.mobile_bar;
self.mobile_bar.dom_element.style.display = show ? 'flex' : 'none';
self.tab_container.dom_element.style.display = show ? 'none' : '';
if (show) self.set_css('tabpanel-mobilebar');
else self.set_css('tabpanel-mobilebar', true);
if (show){
// 手机首屏:当前 tab 不在底部栏(如桌面默认的 home/商店页)时
// 自动切到第一项产线工作界面1 次都不用点)
var known = false;
for (var i=0;i<self._mobile_bar_descs.length;i++){
if (self._mobile_bar_descs[i].name === self.cur_tab_name){ known = true; break; }
}
if (!known && self._mobile_bar_descs.length){
self._mobile_bar_clicked({name: self._mobile_bar_descs[0].name});
} else {
self.mobile_bar.set_active(self.cur_tab_name);
}
}
};
bricks.on_mobile_mode(this._mobile_bar_apply);
this._mobile_bar_apply(bricks.is_mobile());
}
_mobile_bar_clicked(item){
// bind 事件经 dispatch 包装为 {params: 数据}Toolbar/Menu 同约定);
// 框架内部直调传裸对象 —— 两种形态都兼容2026-09-08 实测:不解包则
// 用户点底部 Tab 静默无响应)
if (item && item.params) item = item.params;
var desc = null;
for (var i=0;i<this._mobile_bar_descs.length;i++){
if (this._mobile_bar_descs[i].name === item.name){ desc = this._mobile_bar_descs[i]; break; }
}
if (!desc) return;
// refresh:false —— 手机底部 Tab 切换保留各页状态(会话/滚动位置),
// 符合手机操作习惯(微信式);桌面侧边栏 open_tab 的"点击即刷新"语义不受影响
this.open_tab({name: desc.name, label: desc.label, url: desc.url,
removable: false, refresh: false});
this.mobile_bar.set_active(desc.name);
}
show_first_tab(){
// 手机视口 + 配了 mobile_bar首屏由底部栏第一项决定如「产线」工作界面
// 不能再 dispatch items[0](桌面默认 tab如商店页—— 否则后者异步构建完成
// 会把手机首屏覆盖掉。
// 注意去重constructor 里 createMobileBar→_mobile_bar_apply 已同步执行过
// _mobile_bar_clickedcur_tab_name 已置为第一项),此处只需兜底(如
// _mobile_bar_apply 未触发点击的场景),避免 open_tab 二次刷新重复拉取。
if (this.opts.mobile_bar && bricks.is_mobile()){
var descs = this._mobile_bar_descs || [];
var cur_known = false;
for (var i=0;i<descs.length;i++){
if (descs[i].name === this.cur_tab_name){ cur_known = true; break; }
}
if (cur_known) return;
if (descs.length){
this._mobile_bar_clicked({name: descs[0].name});
return;
}
}
var first = this.opts.items[0];
if (!first) return;
// 按钮已建好Toolbar createTools 完成后的 ready 再入)→ click 走 do_handle
// 内容切换 + active 高亮一起更新按钮还没建好constructor 首次同步调用)→
// dispatch command 先加载内容,高亮由 ready 后的 click 补上
for (var i=0;i<this.toolbar.toolList.length;i++){
if (this.toolbar.toolList[i].tool_opts.name === first.name){
this.toolbar.click(first.name);
return;
}
}
this.toolbar.dispatch('command', first);
}
createToolbar(){
var desc = {
tools:this.opts.items
};
var orient;
if (this.opts.tab_pos == 'top' || this.opts.tab_pos == 'bottom'){
orient = 'horizontal';
} else {
orient = 'vertical';
}
desc.orientation = orient;
this.toolbar = new bricks.Toolbar(desc);
this.toolbar.bind('command', this.show_tabcontent.bind(this));
this.toolbar.bind('remove', this.tab_removed.bind(this));
this.toolbar.bind('ready', this.show_first_tab.bind(this));
this.tab_container.add_widget(this.toolbar);
}
async show_tabcontent(event){
var tdesc = event.params;
var items = this.opts.items;
if (tdesc.name == this.cur_tab_name){
bricks.debug('TabPanel(): click duplication click on same tab', tdesc)
return;
}
for (var i=0;i<items.length;i++){
if (tdesc.name == items[i].name){
var w = this.content_buffer[tdesc.name];
// refresh:true 的 tab 每次切换都重建(原语义保留)
if (w && items[i].refresh){
delete this.content_buffer[tdesc.name];
w = null;
}
if (!w){
var c = items[i].content || {"widgettype":"urlwidget","options":{"url":items[i].url}};
try {
if (c instanceof bricks.JsWidget){
w = c;
} else {
w = await bricks.widgetBuild(c, this, {});
}
}
catch (e) {
console.log('except ', e)
}
if (! w){
bricks.debug('TabPanel():create content error', c);
return;
}
this.content_buffer[tdesc.name] = w;
}
this._push_tab_history(tdesc.name);
this.cur_tab_name = tdesc.name;
this.switch_content(w);
return;
}
}
bricks.debug('TabPanel(): click event handled but noting to do', tdesc)
}
switch_content(w){
this.content_container.clear_widgets();
this.content_container.add_widget(w);
this.dispatch('switch', w);
w.dispatch('active');
}
// 动态 tab 支持(原 pipeline-app index_tab.js 补丁2026-09-07 收编入框架):
// 异步加载 tab content —— 不阻塞 open_tab401 触发登录框,登录后重试。
async _load_tab_content(desc){
if (this.content_buffer[desc.name]){
if (this.cur_tab_name === desc.name) this.switch_content(this.content_buffer[desc.name]);
return;
}
var c = desc.content || {"widgettype":"urlwidget","options":{"url":desc.url}};
var w = await bricks.widgetBuild(c, this, {});
this.content_buffer[desc.name] = w;
if (w && this.cur_tab_name === desc.name) this.switch_content(w);
}
// 动态打开 tab已存在则切换默认刷新内容desc.refresh===false 用缓存),
// 不存在则新增。content 异步加载不阻塞;新增时等 createTool 真正建出按钮
// 再 toolbar.click —— createTool 是 asyncawait widgetBuild若同步 click
// 新按钮还没进 toolListclick 空转 → active 高亮不切换2026-09-08 bugfix
async open_tab(desc){
var existing = null;
for (var i=0;i<this.opts.items.length;i++){
if (this.opts.items[i].name === desc.name){ existing = this.opts.items[i]; break; }
}
if (existing){
var need_refresh = desc.refresh !== false;
if (desc.name !== this.cur_tab_name){
this._push_tab_history(desc.name);
this.cur_tab_name = desc.name;
if (this.toolbar && this.toolbar.click) this.toolbar.click(desc.name);
}
if (need_refresh){
delete this.content_buffer[desc.name];
this._load_tab_content(existing);
} else {
var w = this.content_buffer[desc.name];
if (w) this.switch_content(w);
else this._load_tab_content(existing);
}
return;
}
// 新增
desc.content = desc.content || {"widgettype":"urlwidget","options":{"url":desc.url}};
this.opts.items.push(desc);
this._push_tab_history(desc.name);
this.cur_tab_name = desc.name;
await this.add_tab(desc);
// cur_tab_name 已提前置位click → do_handle 更新按钮高亮,
// 派发的 command 到 show_tabcontent 时同名早退,不会重复加载 content
if (this.toolbar && this.toolbar.click) this.toolbar.click(desc.name);
this._load_tab_content(desc);
}
add_tab(desc){
// createTool 内部已按 desc.removable 调 Toolbar.add_removable 挂删除按钮;
// TabPanel 自身无 add_removeable 方法(旧实现调它会 TypeError
// 2026-09-07 收编 index_tab.js 修复时一并纠正)
return this.toolbar.createTool(desc);
}
// tab 访问历史(删 tab 后切回「上一个当前页」用)
_push_tab_history(name){
if (!this.tab_history) this.tab_history = [];
this.tab_history.remove(name);
this.tab_history.push(name);
}
// 删除 tab 后的下一个 tab历史里最近访问且仍在 items 的;无则第一个
_next_tab_after_remove(){
var items = this.opts.items;
var hist = this.tab_history || [];
for (var i=hist.length-1;i>=0;i--){
for (var j=0;j<items.length;j++){
if (items[j].name === hist[i]) return items[j].name;
}
}
return items.length ? items[0].name : null;
}
tab_removed(event){
var desc = event.params;
var name = desc.name;
if (this.content_buffer.hasOwnProperty(name)){
var w = this.content_buffer[name];
delete this.content_buffer[name];
// 释放被删 tab 的内容 widget含定时器/事件,防泄漏)
if (w && w.destroy) { try { w.destroy(); } catch(e){} }
}
// items 同步移除:否则 open_tab/show_tabcontent 仍把它当存在的 tab
for (var i=0;i<this.opts.items.length;i++){
if (this.opts.items[i].name === name){ this.opts.items.splice(i,1); break; }
}
if (this.tab_history) this.tab_history.remove(name);
if (name == this.cur_tab_name){
// 清空 cur_tab_name让 toolbar.click → show_tabcontent 不早退,
// 且 do_handle 真正切换内容 + 更新按钮高亮
this.cur_tab_name = '';
var next = this._next_tab_after_remove();
if (next){
if (this.toolbar && this.toolbar.click) this.toolbar.click(next);
} else {
// 没有剩余 tab内容区一并清空原实现漏掉 → 内容残留)
this.content_container.clear_widgets();
}
}
}
}
bricks.Factory.register('TabPanel', bricks.TabPanel);