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)!)      ))    ))  }}