硬核数字看板:ArkUI 深色布局让鸿蒙库存页像飞机驾驶舱


实例:库存进销存(Inventory)|风格:深色数字面板
一、设计理念:为什么库存管理要用深色界面
前五个实例的页面都是浅色清新风(白底、浅灰、柔和色),实例 6 第一次采用深色数字面板风格——页面背景浅灰,核心统计区是深蓝黑渐变,数字用荧光色(绿/黄/红)呈现。为什么库存管理适合深色?
- 数字优先:库存管理者的核心诉求是「一眼扫过所有数字」——荧光绿(库存充足)、琥珀黄(总量)、警示红(预警)在深色背景上对比度极高,数字可读性远超浅色底;
- 驾驶舱隐喻:飞机驾驶舱、交易终端都是深色背景 + 高亮数字——因为深色减少视觉干扰,让注意力集中在数据本身;
- 警示氛围:库存预警是「紧张感」场景,深色基调天然传递「需要关注」的情绪。
页面信息架构:
- 标题栏:进销存标题 + 事务标语 + 刷新;
- 深色统计面板:商品种类 / 库存总件数 / 预警商品 三列大数字;
- 库存清单:每行商品 + 当前库存 + 预警状态 + 出入库按钮;
- 最近流水:出入库历史(方向图标 + 商品名 + 数量 + 金额);
- 出入库弹窗:选择类型(入库/出库)+ 数量 + 单价。
二、页面骨架:Column + Scroll
本实例没有右下角悬浮按钮(出入库操作在每行商品上),所以根节点用 Column 而非 Stack:
build() {
Column() {
// 标题栏
Row() { ... }
// 可滚动内容
Scroll() {
Column() {
// 深色统计面板
// 库存清单
// 最近流水
}
}
.width('100%').layoutWeight(1).scrollBar(BarState.Off)
}
.width('100%').height('100%').backgroundColor('#F1F5F9')
// ===== 出入库弹窗 =====
if (this.opVisible) {
Column() { ... }
}
}
注意弹窗 if (this.opVisible) 在 Column 内部(根节点下方)——ArkTS 要求 @Entry 组件的 build 只能有一个根节点,弹窗必须放进根容器内。这是早期版本踩过的坑(弹窗写在 build 最外层导致 build method can have only one root node 编译错误),已修正。
三、深色统计面板:荧光三数字
统计面板是页面的视觉核心,深蓝黑渐变 + 三列荧光数字:
Row() {
Column() {
Text(String(this.total)).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#4ADE80')
Text('商品种类').fontSize(11).fontColor('#94A3B8').margin({ top: 2 })
}.layoutWeight(1)
Column() {
Text(String(this.stockSum)).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#FBBF24')
Text('库存总件数').fontSize(11).fontColor('#94A3B8').margin({ top: 2 })
}.layoutWeight(1)
Column() {
Text(String(this.warnCount)).fontSize(28).fontWeight(FontWeight.Bold)
.fontColor(this.warnCount > 0 ? '#F87171' : '#4ADE80')
Text('预警商品').fontSize(11).fontColor('#94A3B8').margin({ top: 2 })
}.layoutWeight(1)
}
.width('94%').padding({ top: 20, bottom: 20 })
.borderRadius(16)
.linearGradient({ angle: 180, colors: [['#0F172A', 0], ['#1E293B', 1]] })
.margin({ top: 10 })
色彩语义:
| 数据 | 正常色 | 警示色 | 含义 |
|---|---|---|---|
| 商品种类 | 荧光绿 #4ADE80 |
— | 数量本身无警示 |
| 库存总件数 | 琥珀黄 #FBBF24 |
— | 总量中性 |
| 预警商品 | 荧光绿 | 警示红 #F87171 |
>0 即红 |
fontColor 三元切换:this.warnCount > 0 ? '#F87171' : '#4ADE80'——有预警就变红,无预警保持绿。数字颜色随状态变化是数据面板的通用设计,让异常一眼可见。
linearGradient({ angle: 180, colors: [['#0F172A', 0], ['#1E293B', 1]] }) 是上下渐变的深色面板底色。
四、库存清单:预警优先 + 出入库入口
库存清单每行包含:状态图标(⚠️/✅)、名称分类、库存数字(预警红)、出入库按钮:
ForEach(this.products, (p: Product) => {
Row({ space: 10 }) {
Text(p.stock <= p.warnLine ? '⚠️' : '✅').fontSize(18)
Column({ space: 2 }) {
Text(p.name).fontSize(15).fontWeight(FontWeight.Medium)
Text(`${p.category} · 单价 ¥${p.price.toFixed(2)}/${p.unit}`).fontSize(11).fontColor('#999999')
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column() {
Text(`${p.stock} ${p.unit}`)
.fontSize(16).fontWeight(FontWeight.Bold)
.fontColor(p.stock <= p.warnLine ? '#DC2626' : '#111827')
Text(p.stock <= p.warnLine ? `低于预警线 ${p.warnLine}` : `预警线 ${p.warnLine}`)
.fontSize(10).fontColor('#999999').margin({ top: 2 })
}
Text('出入库').fontSize(12).padding({ left: 10, right: 10, top: 5, bottom: 5 })
.borderRadius(12).backgroundColor('#3B82F6').fontColor(Color.White)
.onClick(() => {
this.opProductId = p.id;
this.opQty = ''; this.opPrice = '';
this.opVisible = true;
})
}
.width('100%').padding(12).backgroundColor(Color.White).borderRadius(10).margin({ top: 8 })
}, (p: Product) => `${p.id}-${p.name}`)
预警双重提示:行首 ⚠️ 图标 + 库存数字红色加粗 + 副标题「低于预警线 X」——三重视觉强化预警商品,运营人员扫一眼就能锁定需要补货的商品。
出入库按钮:点击记录 opProductId(当前商品)并清空表单,打开弹窗。每次打开都重置输入,避免上次操作残留数据。
五、最近流水列表:方向 + 商品 + 金额
流水列表展示最近 15 条出入库记录:
ForEach(this.flows, (f: StockFlow) => {
Row({ space: 10 }) {
Text(f.type === 0 ? '📥' : '📤').fontSize(18)
Column({ space: 2 }) {
Text(`${f.productName} × ${f.quantity}`).fontSize(14).fontWeight(FontWeight.Medium)
Text(this.fmtTime(f.flowTime)).fontSize(11).fontColor('#999999')
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text(`${f.type === 0 ? '+' : '-'}${f.totalPrice.toFixed(2)}`)
.fontSize(14).fontWeight(FontWeight.Bold)
.fontColor(f.type === 0 ? '#059669' : '#DC2626')
}
.width('100%').padding(12).backgroundColor(Color.White).borderRadius(10).margin({ top: 8 })
}, (f: StockFlow) => `${f.id}-${f.flowTime}`)
方向语义:📥 入库(绿色 + 金额)、📤 出库(红色 - 金额)。金额是 totalPrice(联表查询已计算好),商品名来自 JOIN 填充的 productName 字段。流水时间用 fmtTime 格式化为「6月15日 14:30」。
六、出入库弹窗:类型切换 + 数量单价
点击任意商品的「出入库」弹出操作表单:
if (this.opVisible) {
Column() {
Text('📦 出入库操作').fontSize(18).fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
Button('入库').layoutWeight(1).backgroundColor(this.opType === 0 ? '#059669' : '#EEF2F7')
.fontColor(this.opType === 0 ? Color.White : '#555555')
.onClick(() => this.opType = 0)
Button('出库').layoutWeight(1).backgroundColor(this.opType === 1 ? '#DC2626' : '#EEF2F7')
.fontColor(this.opType === 1 ? Color.White : '#555555')
.onClick(() => this.opType = 1)
}.margin({ top: 12 })
TextInput({ placeholder: '数量 *', text: this.opQty }).type(InputType.Number)
.margin({ top: 10 }).onChange((v: string) => this.opQty = v)
TextInput({ placeholder: '单价(留空用进价)', text: this.opPrice }).type(InputType.Number)
.margin({ top: 8 }).onChange((v: string) => this.opPrice = v)
Row({ space: 8 }) {
Button('取消').layoutWeight(1).backgroundColor('#EEF2F7').fontColor('#555555')
.onClick(() => this.opVisible = false)
Button('确认').layoutWeight(1).backgroundColor('#3B82F6')
.onClick(() => this.doStockInOut())
}.margin({ top: 16 })
}
.padding(20).borderRadius(16).backgroundColor(Color.White).width('88%')
.position({ x: '6%', y: '30%' })
}
交互要点:
- 入库/出库按钮互斥高亮(opType 0/1),绿色入库红色出库;
- 数量
InputType.Number数字键盘;单价留空则用商品进价(doStockInOut里price > 0 ? price : product.price兜底); - 确认后调用
doStockInOut(),内部走 6-3 文章将讲的事务流程。
七、刷新与状态
async refresh(): Promise<void> {
try {
await InventoryDao.initSeedData(this.context);
this.products = await InventoryDao.queryProducts(this.context);
this.flows = await InventoryDao.queryFlows(this.context, 15);
const stats: InventoryStats = await InventoryDao.statistics(this.context);
this.total = stats.total;
this.stockSum = stats.stockSum;
this.warnCount = stats.warnCount;
} catch (e) {
promptAction.showToast({ message: `加载失败: ${e}` });
}
}
四个数据源一次刷新:商品列表、最近流水、三个统计数字。出入库成功后调用 refresh 全量更新——列表、流水、统计面板实时同步。
八、UI 风格要素一览
| 风格项 | 取值 | 说明 |
|---|---|---|
| 页面背景 | #F1F5F9 浅冷灰 |
衬托白卡与深面板 |
| 统计面板 | 深蓝黑渐变 #0F172A→#1E293B |
驾驶舱质感 |
| 荧光数字 | 绿 #4ADE80 / 黄 #FBBF24 / 红 #F87171 |
高对比数据 |
| 预警红 | #DC2626 库存数字 |
缺货标红 |
| 入库绿 | #059669 |
正向语义 |
| 出库红 | #DC2626 |
负向语义 |
| 主色 | #3B82F6 蓝 |
操作按钮 |
九、文章小结
本篇文章完成了实例 6 的 UI 层:深色数字面板(荧光三数字 + 渐变背景)+ 预警优先库存清单 + 出入库流水列表 + 操作弹窗。设计核心是「深色 + 高对比数字」让数据可读性最大化,预警三重视觉强化让缺货商品一眼锁定。所有操作(出入库)都收敛到弹窗,由 6-3 的事务逻辑保证数据一致。
下一篇(6-3)是实例 6 的技术深水区——TRANSACTION 事务如何保证「流水 + 库存」原子更新,以及库存不足的预检回滚。
十、常见问题 FAQ
Q1:为什么本实例不用 Stack 根节点(没有悬浮按钮)?
A:因为出入库操作是「每行商品的按钮」,不是全局悬浮动作——没有元素需要叠在内容之上。根节点用 Column 更简洁。根容器选择取决于是否有悬浮元素,这是 ArkUI 布局的第一个决策点。
Q2:统计面板的数字为什么是 28fp 大字号?
A:驾驶舱隐喻的核心——大数字让「扫一眼」成为可能。仓管员看板不需要小字说明,需要的是「库存 377、预警 4」这种即时可读的硬数据。字号与信息层级强相关,关键数字用大字号是数据面板铁律。
Q3:预警商品为什么排在列表顶部?
A:数据层 orderByAsc('stock')(库存升序)让库存最少的商品自然浮到顶部,无需前端二次排序。排序逻辑下沉到 SQL 是本项目的一贯原则——页面拿到即终态。
Q4:出入库弹窗每次打开为什么要清空输入?
A:避免「上次操作残留」——如果上次输了 10 件取消,下次打开还显示 10,用户容易误确认。清空输入是模态表单的卫生习惯。
Q5:流水只显示 15 条,更多历史怎么看?
A:当前 Demo 用 queryFlows(context, 15) 限制条数,简化列表。生产环境应加分页(LIMIT/OFFSET),实例 8(商品分页列表)会专门讲分页——这里的 15 条限制是「分页雏形」。
Q6:深色面板在浅色页面里会不会突兀?
A:不会——这正是「视觉锚点」设计:整页浅色,唯独统计面板深色,用户的视线自然被深色区域吸引。用对比创造焦点,深色面板是刻意的视觉中心而非不协调。
Q7:出入库按钮放行内会不会误触?
A:行内按钮是明确的「操作入口」,配合弹窗二次确认(先选类型再输入数量),误触成本低。如果担心误触,可改为「左滑显示操作」——但复杂手势在 Demo 阶段收益低,保持直接按钮。
Q8:fmtTime 的格式为什么是「6月15日 14:30」?
A:中文场景的友好格式——去掉年份(流水列表都是近期的,年份冗余),保留月日时分。时间格式是产品细节,这里展示「格式化函数」的思想:时间戳 → 可读文案的转换集中在工具方法,便于统一调整。
更多推荐




所有评论(0)