android-report-tables
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRequired Plugins
所需插件
Superpowers plugin: MUST be active for all work using this skill. Use throughout the entire build pipeline — design decisions, code generation, debugging, quality checks, and any task where it offers enhanced capabilities. If superpowers provides a better way to accomplish something, prefer it over the default approach.
Superpowers插件: 所有使用本技能的工作都必须启用该插件。请在整个构建流程中使用它——包括设计决策、代码生成、调试、质量检查,以及所有它能提供增强能力的任务。如果Superpowers能提供更优的实现方式,请优先使用它而非默认方案。
Android Report Tables (25+ Rows)
Android报表表格(行数≥25)
When a report can exceed 25 rows, it must be rendered as a table, not card lists. This prevents scroll fatigue and preserves scanability for business data.
当报表行数可能超过25行时,必须以表格而非卡片列表形式渲染。这可以避免滚动疲劳,保障业务数据的可扫视性。
Scope
适用范围
Use for: Reports, analytics lists, financial summaries, inventory reports, audit logs, and any dataset likely to exceed 25 rows.
Do not use: Small datasets (<=25 rows) or highly visual summaries where cards communicate state better.
适用场景: 报表、分析列表、财务汇总、库存报表、审计日志,以及所有可能超过25行的数据集。
不适用场景: 小型数据集(≤25行),或者卡片能更好传达状态的高可视化汇总场景。
Rule (Mandatory)
规则(强制要求)
- If a report can exceed 25 rows, use a table layout.
- Cards are acceptable only when the dataset is guaranteed <=25 rows.
- 若报表可能超过25行,请使用表格布局。
- 仅当数据集确认≤25行时,才允许使用卡片。
Existing ReportTable Composable
现有ReportTable可组合函数
The project has a reusable at :
ReportTable<T>core/ui/components/ReportTable.ktkotlin
ReportTable(
columns = listOf(
TableColumn(header = "#", weight = 0.4f) { "#${it.rank}" },
TableColumn(header = "Name", weight = 1.5f) { it.fullName ?: "-" },
TableColumn(header = "Inv", weight = 0.4f) { it.totalInvoices.toString() },
TableColumn(header = "Amount", weight = 1.2f) { "$currency ${fmt.format(it.totalAmount)}" }
),
rows = report.rows,
onRowClick = { /* optional */ },
pageSize = 25
)Features:
- Generic with
<T>definitions (header, weight, value lambda)TableColumn<T> - Built-in client-side pagination (25/page default)
- Header row with background
surfaceVariant - for proportional column sizing
Modifier.weight() - Empty state with string resource
项目在路径下提供了可复用的:
core/ui/components/ReportTable.ktReportTable<T>kotlin
ReportTable(
columns = listOf(
TableColumn(header = "#", weight = 0.4f) { "#${it.rank}" },
TableColumn(header = "Name", weight = 1.5f) { it.fullName ?: "-" },
TableColumn(header = "Inv", weight = 0.4f) { it.totalInvoices.toString() },
TableColumn(header = "Amount", weight = 1.2f) { "$currency ${fmt.format(it.totalAmount)}" }
),
rows = report.rows,
onRowClick = { /* optional */ },
pageSize = 25
)功能特性:
- 支持泛型,可自定义
<T>定义(表头、权重、值获取lambda)TableColumn<T> - 内置客户端分页(默认25条/页)
- 表头行使用背景
surfaceVariant - 支持实现列宽比例分配
Modifier.weight() - 内置字符串资源的空状态
Date Display (Mandatory)
日期展示规则(强制要求)
All dates in report tables MUST be human-readable. Never display raw API dates like . Always format to short readable form: (e.g., ).
2026-02-14d MMM yyyy14 Feb 2026报表表格中的所有日期必须是人类可读格式。 永远不要展示原始API日期例如,请始终格式化为简短易读的形式:(例如)。
2026-02-14d MMM yyyy14 Feb 2026Standard Date Formatter Pattern
标准日期格式化器示例
kotlin
val apiDateFmt = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) }
val displayDateFmt = remember { SimpleDateFormat("d MMM yyyy", Locale.US) }
val formatDate: (String) -> String = { raw ->
try { displayDateFmt.format(apiDateFmt.parse(raw)!!) } catch (_: Exception) { raw }
}
// Usage in TableColumn:
TableColumn("Date", minWidth = 100.dp) { formatDate(it.date) }
TableColumn("Oldest", minWidth = 100.dp) { it.oldestDate?.let { formatDate(it) } ?: "-" }kotlin
val apiDateFmt = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) }
val displayDateFmt = remember { SimpleDateFormat("d MMM yyyy", Locale.US) }
val formatDate: (String) -> String = { raw ->
try { displayDateFmt.format(apiDateFmt.parse(raw)!!) } catch (_: Exception) { raw }
}
// Usage in TableColumn:
TableColumn("Date", minWidth = 100.dp) { formatDate(it.date) }
TableColumn("Oldest", minWidth = 100.dp) { it.oldestDate?.let { formatDate(it) } ?: "-" }Rules
规则
- API sends dates as — this is for transport only, never for display
yyyy-MM-dd - Tables, cards, summaries, and any user-facing text must use
d MMM yyyy - Chart axes may use shorter formats like (e.g.,
MMM d) for spaceFeb 14 - Nullable dates: format if present, show if null
-
- API返回的格式日期仅用于数据传输,绝对不能直接展示给用户
yyyy-MM-dd - 表格、卡片、汇总以及所有用户可见文本必须使用格式
d MMM yyyy - 图表坐标轴为了节省空间可以使用更短的格式,例如(如
MMM d)Feb 14 - 可空日期:存在则格式化,为空则展示
-
Portrait Responsiveness Standards
竖屏响应式标准
Column Priority (Phone Portrait)
列优先级(手机竖屏)
- 3-4 columns max for portrait without horizontal scroll
- Abbreviate headers: "#" not "Rank", "Inv" not "Invoices", "Amt" not "Amount", "Bal" not "Balance"
- Use ratios: narrow columns (0.3-0.5f), name columns (1.3-1.5f), amount columns (1.0-1.2f)
weight
- 竖屏无横向滚动时最多展示3-4列
- 缩写表头:用"#"代替"Rank",用"Inv"代替"Invoices",用"Amt"代替"Amount",用"Bal"代替"Balance"
- 使用比例分配:窄列(0.3-0.5f),名称列(1.3-1.5f),金额列(1.0-1.2f)
weight
Weight Guidelines
权重参考标准
| Column Type | Weight | Examples |
|---|---|---|
| Index/Rank | 0.3-0.5f | #, Rank |
| Short text | 0.4-0.6f | Code, Qty, Inv |
| Name/Description | 1.3-1.5f | Product, Distributor |
| Currency amount | 1.0-1.2f | Amount, Balance, Due |
| Date | 0.8-1.0f | Date |
| 列类型 | 权重 | 示例 |
|---|---|---|
| 索引/排名 | 0.3-0.5f | #, 排名 |
| 短文本 | 0.4-0.6f | 编码, 数量, 发票 |
| 名称/描述 | 1.3-1.5f | 产品, 经销商 |
| 货币金额 | 1.0-1.2f | 金额, 余额, 应付款 |
| 日期 | 0.8-1.0f | 日期 |
Horizontal Scroll (5+ columns)
横向滚动(≥5列时)
When a table needs 5+ columns and cannot fit in portrait:
kotlin
Column(Modifier.horizontalScroll(rememberScrollState())) {
ReportTable(columns = ..., rows = ...)
}当表格需要展示5列及以上,无法在竖屏中完整容纳时:
kotlin
Column(Modifier.horizontalScroll(rememberScrollState())) {
ReportTable(columns = ..., rows = ...)
}String Resources
字符串资源
Always use for table headers. Never hardcode header text.
stringResource(R.string.report_col_*)所有表格表头必须使用,绝对不要硬编码表头文本。
stringResource(R.string.report_col_*)Cards vs Tables Decision Matrix
卡片vs表格决策矩阵
| Criteria | Use Cards | Use Table |
|---|---|---|
| Max rows <= 25 guaranteed | Yes | Optional |
| Max rows > 25 possible | No | Required |
| DPCs (5-20 items) | Yes | Optional |
| Daily summary (7 days) | Yes | Optional |
| Distributor lists | No | Required |
| Product lists | No | Required |
| Invoice lists | No | Required |
| Debtors lists | No | Required |
| Top 100 rankings | No | Required |
| 判断标准 | 使用卡片 | 使用表格 |
|---|---|---|
| 确认最大行数≤25 | 是 | 可选 |
| 最大行数可能>25 | 否 | 必须 |
| DPCs(5-20项) | 是 | 可选 |
| 每日汇总(7天) | 是 | 可选 |
| 经销商列表 | 否 | 必须 |
| 产品列表 | 否 | 必须 |
| 发票列表 | 否 | 必须 |
| 债务人列表 | 否 | 必须 |
| 前100名排名 | 否 | 必须 |
Pagination Guidance
分页指引
- Default to client-side pagination for up to a few hundred rows (25 per page).
- handles pagination internally — no need for ViewModel pagination.
ReportTable - For larger datasets (1000+), use server pagination via API offset/limit params.
- 最多几百行的数据集默认使用客户端分页(25条/页)。
- 内部已处理分页逻辑——无需在ViewModel中额外实现分页。
ReportTable - 更大的数据集(≥1000行)请通过API的offset/limit参数实现服务端分页。
Pull-to-Refresh (Mandatory)
下拉刷新(强制要求)
Every screen that displays reports, statistics, or data MUST support pull-to-refresh. Users expect to swipe down to reload current data.
所有展示报表、统计数据的页面必须支持下拉刷新。用户期望可以通过向下滑动重新加载当前数据。
Implementation Pattern (PullToRefreshBox)
实现模式(PullToRefreshBox)
kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyReportScreen(viewModel: MyViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(uiState.loading) {
if (!uiState.loading) isRefreshing = false
}
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
// Report content
}
}
}kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyReportScreen(viewModel: MyViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(uiState.loading) {
if (!uiState.loading) isRefreshing = false
}
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
// Report content
}
}
}Rules
规则
- ViewModel MUST expose a public /
reload()functionrefresh() - Hub screens (Sales Hub, Network Hub, etc.) refresh their statistics/charts
- Report screens refresh their data (re-fetch from API)
- Dashboard refreshes KPI cards
- Use (simpler API than the older
PullToRefreshBox)PullToRefreshContainer
- ViewModel必须暴露公开的/
reload()方法refresh() - 中心页面(销售中心、网络中心等)需要刷新其统计数据/图表
- 报表页面需要刷新其数据(重新从API拉取)
- 仪表盘页面需要刷新KPI卡片
- 优先使用(比旧版
PullToRefreshBoxAPI更简洁)PullToRefreshContainer
Screen Structure Pattern
页面结构模式
Report screens with tables should use a scrollable (not ), since is not a lazy composable. Wrap in :
ColumnLazyColumnReportTablePullToRefreshBoxkotlin
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Filters
// Summary cards
// ReportTable (handles its own pagination)
}
}包含表格的报表页面请使用可滚动的(不要用),因为不是懒加载可组合函数。外层包裹:
ColumnLazyColumnReportTablePullToRefreshBoxkotlin
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Filters
// Summary cards
// ReportTable (handles its own pagination)
}
}Checklist
检查清单
- If report can exceed 25 rows, use ReportTable composable
- Limit to 3-4 columns for portrait, abbreviate headers
- Use with appropriate ratios
Modifier.weight() - Use for all header text
stringResource() - Use Column wrapper (not LazyColumn)
verticalScroll - Let ReportTable handle pagination (remove ViewModel pagination logic)
- Pull-to-refresh on every screen with reports or statistics
- 若报表可能超过25行,使用ReportTable可组合函数
- 竖屏场景限制为3-4列,缩写表头
- 使用设置合理的列宽比例
Modifier.weight() - 所有表头文本使用
stringResource() - 使用带的Column容器(不要用LazyColumn)
verticalScroll - 让ReportTable处理分页逻辑(移除ViewModel中的分页逻辑)
- 所有展示报表或统计数据的页面支持下拉刷新