InfoRadar / client /src /ts /controllers /layoutController.ts
dqy08's picture
minimap alpha
40400a1
import * as d3 from 'd3';
import { isNarrowScreen } from '../utils/responsive';
export type LayoutState = {
sidebar: {
width: number;
visible: boolean;
};
};
export type LayoutControllerOptions = {
sidebarState: LayoutState['sidebar'];
sideBar: d3.Selection<any, unknown, any, any>;
sidebarBtn: d3.Selection<any, unknown, any, any>;
onSidebarToggle?: (visible: boolean) => void;
onLayoutChange?: () => void;
};
export class LayoutController {
private options: LayoutControllerOptions;
private isResizing = false;
private startX = 0;
private startWidth = 0;
private leftPanelRatio = 0.5; // 左侧面板的比例,默认50%
constructor(options: LayoutControllerOptions) {
this.options = options;
this.initialize();
}
private initialize(): void {
this.setupSidebar();
this.setupWindowResize();
this.setupPanelResizer();
this.reLayout(window.innerWidth, window.innerHeight);
}
private setupSidebar(): void {
this.options.sidebarBtn.on('click', () => {
const sb = this.options.sidebarState;
sb.visible = !sb.visible;
this.options.sidebarBtn.classed('on', sb.visible);
this.options.sideBar.classed('hidden', !sb.visible);
this.options.sideBar.style('right',
sb.visible ? null : `-${this.options.sidebarState.width}px`);
if (this.options.onSidebarToggle) {
this.options.onSidebarToggle(sb.visible);
}
this.reLayout();
});
}
private setupWindowResize(): void {
window.onresize = () => {
const w = window.innerWidth;
const h = window.innerHeight;
this.reLayout(w, h);
if (this.options.onLayoutChange) {
this.options.onLayoutChange();
}
};
}
public reLayout(w = window.innerWidth, h = window.innerHeight): void {
d3.selectAll('.sidenav')
.style('height', (h - 53) + 'px');
const sb = this.options.sidebarState;
const mainWidth = w - (sb.visible ? sb.width : 0);
// 检测是否是移动端/窄屏模式
const isMobile = isNarrowScreen();
const mainFrame = d3.selectAll('.main_frame');
if (isMobile) {
// 移动端:不设置固定高度,让CSS的height: auto生效,允许body滚动
mainFrame
.style('height', null) // 移除内联样式,让CSS生效
.style('width', null); // 移除内联宽度样式,让CSS生效
} else {
// 桌面端:设置固定高度
mainFrame
.style('height', (h - 53) + 'px')
.style('width', mainWidth + 'px');
// 根据保存的比例重新计算左侧面板宽度
this.updateLeftPanelWidth(mainWidth);
}
}
/**
* 根据当前窗口宽度和保存的比例更新左侧面板宽度
*/
private updateLeftPanelWidth(containerWidth: number): void {
const leftPanel = d3.select('.left_panel');
if (leftPanel.empty()) return;
// 计算可用宽度(减去分割线宽度8px)
const availableWidth = containerWidth - 8;
// 根据比例计算左侧面板宽度
const leftWidth = availableWidth * this.leftPanelRatio;
// 确保宽度在最小和最大限制内
const minWidth = containerWidth * 0.1;
const maxWidth = containerWidth * 0.9;
const clampedWidth = Math.max(minWidth, Math.min(maxWidth, leftWidth));
// 更新比例(如果被限制,则更新比例以保持一致性)
this.leftPanelRatio = clampedWidth / availableWidth;
leftPanel.style('flex-basis', clampedWidth + 'px');
}
private setupPanelResizer(): void {
const resizer = d3.select('#resizer');
const leftPanel = d3.select('.left_panel');
// 初始化左侧面板宽度(使用默认比例50%)
const sb = this.options.sidebarState;
const mainWidth = window.innerWidth - (sb.visible ? sb.width : 0);
this.updateLeftPanelWidth(mainWidth);
resizer.on('mousedown', (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
this.isResizing = true;
this.startX = event.clientX;
// 获取当前左侧面板的实际宽度
const currentFlexBasis = leftPanel.style('flex-basis');
this.startWidth = parseInt(currentFlexBasis) || (mainWidth * this.leftPanelRatio);
d3.select('body')
.style('cursor', 'col-resize')
.style('user-select', 'none');
d3.select(window)
.on('mousemove.resizer', (ev: MouseEvent) => this.handleMouseMove(ev, leftPanel))
.on('mouseup.resizer', () => this.handleMouseUp());
});
}
private handleMouseMove(event: MouseEvent, leftPanel: d3.Selection<any, unknown, any, any>): void {
if (!this.isResizing) return;
event.preventDefault();
const sb = this.options.sidebarState;
const containerWidth = window.innerWidth - (sb.visible ? sb.width : 0);
const availableWidth = containerWidth - 8; // 减去分割线宽度
const deltaX = event.clientX - this.startX;
const newWidth = Math.max(
containerWidth * 0.1,
Math.min(containerWidth * 0.9, this.startWidth + deltaX)
);
// 更新左侧面板宽度
leftPanel.style('flex-basis', newWidth + 'px');
// 更新保存的比例
this.leftPanelRatio = newWidth / availableWidth;
}
private handleMouseUp(): void {
if (!this.isResizing) return;
this.isResizing = false;
d3.select('body')
.style('cursor', 'default')
.style('user-select', 'auto');
d3.select(window)
.on('mousemove.resizer', null)
.on('mouseup.resizer', null);
}
}