REAL SHOWCASE SOURCEreport-view-runtime
1 个文件 · 150 行 · TypeScript
// ===== showcase/src/examples/components/report_view.ts ===== import { RenderPageHeader, RenderReportView, RenderStackPanel, RenderText, type ReportExportSnapshot, type ReportMaterializedCell, type ReportMergeInfo, type ReportSnapshotOptions, type ReportViewportRequest, type ReportViewportSlice, type ReportVirtualModel,} from 'ds-ui' const reportColumns = ['检验编号', '患者姓名', '检验项目', '结果', '参考范围', '状态']const testNames = ['空腹血糖', '总胆固醇', '血红蛋白', '白细胞计数', '肌酐'] export function createReportViewExample(): RenderStackPanel { const status = new RenderText('冻结首行和首列;点击或拖动可选择单元格区域。', { role: 'secondary' }) const report = new RenderReportView({ virtualModel: new MedicalLabVirtualModel(25_000), height: 500, showGridHeaders: true, onSelectionChange: selection => { status.text = selection ? `当前选择:R${selection.startRow + 1}C${selection.startColumn + 1} - R${selection.endRow + 1}C${selection.endColumn + 1}` : '当前未选择单元格。' }, }) const panel = new RenderStackPanel({ orientation: 'vertical', spacing: 14, crossAxisAlignment: 'stretch' }) panel.addChild(new RenderPageHeader({ title: 'ReportView 检验结果运行报表', description: '25,000 行虚拟数据按视口物化,支持冻结区域、双向滚动、选区与复制。', })) panel.addChild(report) panel.addChild(status) panel.padding = 24 return panel} class MedicalLabVirtualModel implements ReportVirtualModel { readonly columnCount = reportColumns.length readonly cellCount: number readonly freeze = { rows: 1, columns: 1, rightColumns: 0 } readonly errors: string[] = [] constructor(readonly rowCount: number) { this.cellCount = rowCount * this.columnCount } getRowHeight(row: number): number { return row === 0 ? 32 : 28 } getColumnWidth(column: number): number { return [132, 112, 128, 88, 112, 84][column] ?? 100 } getCell(row: number, column: number): ReportMaterializedCell | null { if (row < 0 || row >= this.rowCount || column < 0 || column >= this.columnCount) return null const rawValue = row === 0 ? reportColumns[column]! : this._value(row, column) const id = row === 0 ? `header-${column}` : `lab-${row}-${column}` return { row, column, rowSpan: 1, columnSpan: 1, templateCell: { id }, templateCellId: id, rawValue, displayValue: String(rawValue), style: row === 0 ? { fontWeight: 'bold', align: 'center' } : column === 3 ? { align: 'right' } : {}, } } getMerge(row: number, column: number): ReportMergeInfo | null { return this.getCell(row, column) ? { row, column, rowSpan: 1, columnSpan: 1, isAnchor: true } : null } getViewport(request: ReportViewportRequest): ReportViewportSlice { const rowStart = Math.max(0, Math.min(this.rowCount, request.rowStart)) const rowEnd = Math.max(rowStart, Math.min(this.rowCount, request.rowEnd)) const columnStart = Math.max(0, Math.min(this.columnCount, request.columnStart)) const columnEnd = Math.max(columnStart, Math.min(this.columnCount, request.columnEnd)) const cells: ReportMaterializedCell[] = [] for (let row = rowStart; row < rowEnd; row += 1) { for (let column = columnStart; column < columnEnd; column += 1) { const cell = this.getCell(row, column) if (cell) cells.push(cell) } } return { rowStart, rowEnd, columnStart, columnEnd, cells } } createSnapshot(options: ReportSnapshotOptions = {}): ReportExportSnapshot { const viewport = options.mode === 'viewport' ? options.viewport : undefined const slice = this.getViewport({ rowStart: viewport?.rowStart ?? 0, rowEnd: viewport?.rowEnd ?? this.rowCount, columnStart: viewport?.columnStart ?? 0, columnEnd: viewport?.columnEnd ?? this.columnCount, }) return { schemaVersion: 2, rowHeights: Array.from({ length: slice.rowEnd - slice.rowStart }, (_, index) => this.getRowHeight(slice.rowStart + index)), columnWidths: Array.from({ length: slice.columnEnd - slice.columnStart }, (_, index) => this.getColumnWidth(slice.columnStart + index)), freeze: { ...this.freeze }, cells: slice.cells.map(cell => ({ row: cell.row, column: cell.column, rowSpan: cell.rowSpan, columnSpan: cell.columnSpan, templateCellId: cell.templateCellId, rawValue: cell.rawValue, displayValue: cell.displayValue, style: cell.style, })), values: this._matrix(slice, cell => cell.displayValue), rawValues: this._matrix(slice, cell => cell.rawValue), errors: [], } } private _value(row: number, column: number): string | number { const ordinal = row.toString().padStart(6, '0') const testIndex = row % testNames.length const result = Number((3.8 + (row * 17) % 85 / 10).toFixed(1)) if (column === 0) return `LAB-${ordinal}` if (column === 1) return `患者 ${ordinal}` if (column === 2) return testNames[testIndex]! if (column === 3) return result if (column === 4) return testIndex === 0 ? '3.9 - 6.1' : '4.0 - 10.0' return result > 9 ? '偏高' : '正常' } private _matrix<T>(slice: ReportViewportSlice, value: (cell: ReportMaterializedCell) => T): T[][] { return Array.from({ length: slice.rowEnd - slice.rowStart }, (_, row) => ( Array.from({ length: slice.columnEnd - slice.columnStart }, (_, column) => ( value(this.getCell(slice.rowStart + row, slice.columnStart + column)!) )) )) }}