Flutter 框架跨平台鸿蒙开发 - 企业报销管理应用
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
一、项目概述
运行效果图




1.1 应用简介
报销管理是一款专为企业员工设计的费用报销管理工具。无论是差旅费用、餐饮支出还是办公用品采购,都能轻松创建报销申请并追踪审批进度。应用支持多级审批流程,费用明细清晰记录,让报销工作规范高效。
应用采用直观的卡片式布局,待审批和已通过金额一目了然。支持按状态和类型筛选,快速定位报销记录。审批功能支持通过和拒绝操作,拒绝时可填写原因,流程透明可追溯。
1.2 核心功能
| 功能模块 | 功能描述 | 实现方式 |
|---|---|---|
| 报销列表 | 展示所有报销记录 | ListView + Card |
| 新建报销 | 创建新的报销申请 | 表单页面 |
| 编辑报销 | 修改已有报销记录 | 表单预填充 |
| 删除报销 | 删除不需要的记录 | 确认对话框 |
| 详情查看 | 查看报销完整信息 | 详情页面 |
| 审批功能 | 通过/拒绝报销申请 | 状态更新 |
| 状态筛选 | 按审批状态筛选 | DropdownButton |
| 类型筛选 | 按报销类型筛选 | DropdownButton |
| 费用明细 | 多项费用详细记录 | 列表管理 |
1.3 报销记录字段
| 字段 | 类型 | 说明 |
|---|---|---|
| 报销标题 | String | 报销单标题,必填 |
| 申请人 | String | 报销申请人,必填 |
| 报销类型 | ExpenseCategory | 差旅/餐饮/交通/办公/其他 |
| 审批状态 | ExpenseStatus | 待审批/已通过/已拒绝 |
| 报销日期 | DateTime | 费用发生日期 |
| 报销金额 | double | 费用合计金额 |
| 报销说明 | String | 报销详细说明 |
| 费用明细 | List | 各项费用详情 |
| 审批人 | String | 审批操作人 |
| 审批时间 | DateTime | 审批操作时间 |
| 拒绝原因 | String | 拒绝时的原因说明 |
1.4 技术栈
| 技术领域 | 技术选型 | 版本要求 |
|---|---|---|
| 开发框架 | Flutter | >= 3.0.0 |
| 编程语言 | Dart | >= 2.17.0 |
| 设计规范 | Material Design 3 | - |
| 状态管理 | setState | - |
| 目标平台 | 鸿蒙OS | API 21+ |
1.5 项目结构
lib/
└── main_expense_claim.dart
├── ExpenseClaimApp # 应用入口
├── ExpenseStatus # 审批状态枚举
├── ExpenseCategory # 报销类型枚举
├── ExpenseClaim # 报销单模型
├── ExpenseItem # 费用明细模型
├── ExpenseClaimPage # 主列表页面
│ ├── _buildStatisticsSection() # 统计区域
│ ├── _buildFilterSection() # 筛选区域
│ ├── _buildClaimsList() # 记录列表
│ └── _buildClaimCard() # 记录卡片
├── ExpenseClaimEditPage # 编辑页面
│ ├── _selectDate() # 日期选择
│ ├── _addItem() # 添加费用项
│ └── _save() # 保存记录
└── ExpenseClaimDetailPage # 详情页面
├── _buildSection() # 信息区块
└── _buildInfoRow() # 信息行
二、系统架构
2.1 整体架构图
2.2 类图设计
2.3 数据流程图
2.4 审批流程
三、核心模块设计
3.1 数据模型设计
3.1.1 审批状态枚举 (ExpenseStatus)
enum ExpenseStatus {
pending, // 待审批
approved, // 已通过
rejected, // 已拒绝
}
3.1.2 报销类型枚举 (ExpenseCategory)
enum ExpenseCategory {
travel, // 差旅
meal, // 餐饮
transport, // 交通
office, // 办公
other, // 其他
}
3.1.3 报销单模型 (ExpenseClaim)
class ExpenseClaim {
final String id; // 唯一标识
String title; // 报销标题
double amount; // 报销金额
ExpenseCategory category; // 报销类型
ExpenseStatus status; // 审批状态
DateTime date; // 报销日期
String description; // 报销说明
String applicant; // 申请人
String? approver; // 审批人
DateTime? approvedAt; // 审批时间
String? rejectReason; // 拒绝原因
List<ExpenseItem> items; // 费用明细
DateTime createdAt; // 创建时间
DateTime updatedAt; // 更新时间
}
3.1.4 费用明细模型 (ExpenseItem)
class ExpenseItem {
final String id; // 唯一标识
String name; // 费用名称
double amount; // 费用金额
String description; // 费用说明
}
3.2 筛选过滤算法
3.2.1 过滤流程
3.2.2 过滤实现
List<ExpenseClaim> get _filteredClaims {
var claims = _claims.toList();
// 状态过滤
if (_selectedStatus != '全部') {
ExpenseStatus? status;
switch (_selectedStatus) {
case '待审批': status = ExpenseStatus.pending; break;
case '已通过': status = ExpenseStatus.approved; break;
case '已拒绝': status = ExpenseStatus.rejected; break;
}
if (status != null) {
claims = claims.where((c) => c.status == status).toList();
}
}
// 类型过滤
if (_selectedCategory != '全部') {
// ... 类型过滤逻辑
}
// 关键词搜索
if (_searchQuery.isNotEmpty) {
claims = claims.where((c) {
return c.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
c.description.toLowerCase().contains(_searchQuery.toLowerCase());
}).toList();
}
claims.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return claims;
}
3.3 金额统计算法
3.3.1 统计计算
// 待审批金额
double get _totalPending {
return _claims
.where((c) => c.status == ExpenseStatus.pending)
.fold(0.0, (sum, c) => sum + c.amount);
}
// 已通过金额
double get _totalApproved {
return _claims
.where((c) => c.status == ExpenseStatus.approved)
.fold(0.0, (sum, c) => sum + c.amount);
}
3.3.2 统计展示
3.4 页面结构设计
3.4.1 列表页面布局
3.4.2 编辑页面布局
┌─────────────────────────────────────────────────────────────┐
│ AppBar: 新建/编辑报销单 [💾 保存] │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 📝 报销标题 * │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 👤 申请人 * | 📅 报销日期 | 📁 报销类型 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 📋 报销说明 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 多行文本输入 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 费用明细 [+ 添加] │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 机票 ¥1,800.00 │ │
│ │ 酒店 ¥1,200.00 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 合计金额 ¥3,580.00 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.5 状态管理
3.5.1 核心状态变量
class _ExpenseClaimPageState extends State<ExpenseClaimPage> {
final List<ExpenseClaim> _claims = []; // 所有报销记录
String _selectedStatus = '全部'; // 选中的状态筛选
String _selectedCategory = '全部'; // 选中的类型筛选
String _searchQuery = ''; // 搜索关键词
}
class _ExpenseClaimEditPageState extends State<ExpenseClaimEditPage> {
final _formKey = GlobalKey<FormState>(); // 表单Key
late TextEditingController _titleController; // 标题控制器
late TextEditingController _descriptionController; // 说明控制器
late TextEditingController _applicantController; // 申请人控制器
late double _totalAmount; // 总金额
late String _selectedCategory; // 选中的类型
late DateTime _selectedDate; // 选中的日期
late List<ExpenseItem> _items; // 费用明细列表
}
3.5.2 状态更新流程
// 审批通过
void _approveClaim(ExpenseClaim claim) {
setState(() {
claim.status = ExpenseStatus.approved;
claim.approver = '当前用户';
claim.approvedAt = DateTime.now();
claim.updatedAt = DateTime.now();
});
}
// 审批拒绝
void _rejectClaim(ExpenseClaim claim, String reason) {
setState(() {
claim.status = ExpenseStatus.rejected;
claim.rejectReason = reason;
claim.approver = '当前用户';
claim.updatedAt = DateTime.now();
});
}
四、UI设计规范
4.1 配色方案
应用采用蓝色主题风格:
| 颜色类型 | 色值 | 用途 |
|---|---|---|
| 主色 | Blue | AppBar、按钮、金额 |
| 待审批 | Orange | 待审批状态标签 |
| 已通过 | Green | 已通过状态标签 |
| 已拒绝 | Red | 已拒绝状态标签 |
| 差旅 | Purple | 差旅类型图标 |
| 餐饮 | Orange | 餐饮类型图标 |
| 交通 | Blue | 交通类型图标 |
| 办公 | Teal | 办公类型图标 |
4.2 状态标签样式
4.2.1 颜色对照表
| 状态 | 颜色 | 示例 |
|---|---|---|
| 待审批 | 橙色 | 待审批 |
| 已通过 | 绿色 | 已通过 |
| 已拒绝 | 红色 | 已拒绝 |
4.3 类型图标样式
| 类型 | 图标 | 颜色 |
|---|---|---|
| 差旅 | flight | 紫色 |
| 餐饮 | restaurant | 橙色 |
| 交通 | directions_car | 蓝色 |
| 办公 | business_center | 青色 |
| 其他 | more_horiz | 灰色 |
4.4 组件规范
4.4.1 统计卡片
┌─────────────────────────────────────┐
│ 📋 待审批 │
│ │
│ ¥3,580.00 │
└─────────────────────────────────────┘
4.4.2 报销卡片
┌─────────────────────────────────────────────────────────────┐
│ ✈️ 北京出差报销 [待审批] │
│ 差旅 | 张三 │
│ │
│ ¥3,580.00 2024-01-15 │
│ │
│ [拒绝] [通过] │
└─────────────────────────────────────────────────────────────┘
4.5 交互设计
4.5.1 操作方式
| 操作 | 手势 | 效果 |
|---|---|---|
| 查看详情 | 点击卡片 | 跳转详情页 |
| 新建报销 | 点击浮动按钮 | 跳转编辑页 |
| 编辑报销 | 点击编辑按钮 | 跳转编辑页 |
| 删除报销 | 点击删除按钮 | 确认后删除 |
| 审批通过 | 点击通过按钮 | 更新状态 |
| 审批拒绝 | 点击拒绝按钮 | 填写原因后更新 |
| 筛选 | 选择下拉选项 | 过滤列表 |
4.5.2 视觉反馈
五、核心功能实现
5.1 列表页面构建
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('报销管理')),
body: Column(
children: [
_buildStatisticsSection(), // 统计区域
_buildFilterSection(), // 筛选区域
Expanded(
child: _filteredClaims.isEmpty
? _buildEmptyState()
: _buildClaimsList(),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _addClaim,
child: const Icon(Icons.add),
),
);
}
5.2 审批功能实现
// 通过审批
void _approveClaim(ExpenseClaim claim) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('审批通过'),
content: const Text('确定要通过此报销申请吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
setState(() {
claim.status = ExpenseStatus.approved;
claim.approver = '当前用户';
claim.approvedAt = DateTime.now();
});
Navigator.pop(context);
},
child: const Text('确定'),
),
],
),
);
}
// 拒绝审批
void _rejectClaim(ExpenseClaim claim) {
final reasonController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('拒绝原因'),
content: TextField(
controller: reasonController,
decoration: const InputDecoration(labelText: '请输入拒绝原因'),
maxLines: 3,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
setState(() {
claim.status = ExpenseStatus.rejected;
claim.rejectReason = reasonController.text;
});
Navigator.pop(context);
},
child: const Text('确定拒绝'),
),
],
),
);
}
5.3 费用明细管理
// 添加费用项
void _addItem() {
showDialog(
context: context,
builder: (context) {
final nameController = TextEditingController();
final amountController = TextEditingController();
return AlertDialog(
title: const Text('添加费用项'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: '费用名称'),
),
TextField(
controller: amountController,
decoration: const InputDecoration(
labelText: '金额',
prefixText: '¥',
),
keyboardType: TextInputType.number,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
setState(() {
_items.add(ExpenseItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: nameController.text,
amount: double.parse(amountController.text),
));
_calculateTotal();
});
Navigator.pop(context);
},
child: const Text('添加'),
),
],
);
},
);
}
// 计算总金额
void _calculateTotal() {
_totalAmount = _items.fold(0.0, (sum, item) => sum + item.amount);
}
5.4 表单验证与保存
void _save() {
if (_formKey.currentState!.validate()) {
final now = DateTime.now();
// 类型转换
ExpenseCategory category;
switch (_selectedCategory) {
case '差旅': category = ExpenseCategory.travel; break;
case '餐饮': category = ExpenseCategory.meal; break;
case '交通': category = ExpenseCategory.transport; break;
case '办公': category = ExpenseCategory.office; break;
default: category = ExpenseCategory.other;
}
final claim = ExpenseClaim(
id: widget.claim?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text,
amount: _totalAmount,
category: category,
status: widget.claim?.status ?? ExpenseStatus.pending,
date: _selectedDate,
description: _descriptionController.text,
applicant: _applicantController.text,
items: _items,
createdAt: widget.claim?.createdAt ?? now,
updatedAt: now,
);
Navigator.pop(context, claim);
}
}
六、报销管理知识
6.1 报销类型划分
6.1.1 常见报销类型
6.1.2 报销流程规范
| 步骤 | 操作人 | 说明 |
|---|---|---|
| 1 | 申请人 | 填写报销单并提交 |
| 2 | 系统 | 生成报销单号 |
| 3 | 审批人 | 审核报销内容 |
| 4 | 审批人 | 通过或拒绝 |
| 5 | 财务 | 处理付款 |
6.2 审批管理要点
6.2.1 审批要点
| 要点 | 说明 |
|---|---|
| 票据完整性 | 检查发票是否齐全 |
| 金额准确性 | 核对金额是否正确 |
| 用途合理性 | 确认费用是否合理 |
| 流程合规性 | 检查是否符合规定 |
6.2.2 审批状态流转
6.3 费用控制原则
6.3.1 控制原则
| 原则 | 说明 |
|---|---|
| 预算控制 | 不超过部门预算 |
| 标准控制 | 符合公司标准 |
| 时效控制 | 及时提交报销 |
| 凭证控制 | 提供有效凭证 |
6.3.2 常见问题处理
七、扩展功能规划
7.1 后续版本规划
7.2 功能扩展建议
7.2.1 数据持久化
// 使用SharedPreferences存储
class ExpenseStorage {
static Future<void> saveClaims(List<ExpenseClaim> claims) async {
final prefs = await SharedPreferences.getInstance();
final jsonList = claims.map((c) => c.toJson()).toList();
await prefs.setString('expense_claims', jsonEncode(jsonList));
}
}
| 功能 | 说明 |
|---|---|
| 本地存储 | 使用SharedPreferences |
| 云端同步 | 支持多设备同步 |
| 数据备份 | 导出JSON格式 |
7.2.2 图片上传
| 功能 | 说明 |
|---|---|
| 拍照上传 | 拍摄票据照片 |
| 相册选择 | 从相册选择图片 |
| 图片压缩 | 压缩后上传 |
7.2.3 导出报表
| 功能 | 说明 |
|---|---|
| 导出Excel | 生成Excel报表 |
| 导出PDF | 生成PDF报表 |
| 邮件发送 | 直接发送邮件 |
八、注意事项
8.1 开发注意事项
-
金额精度:使用double类型注意精度问题
-
状态同步:审批后及时更新状态
-
数据验证:表单输入要进行验证
-
用户体验:操作后给予反馈提示
8.2 用户体验优化
💡 用户体验建议 💡
- 审批操作简洁明了
- 费用明细清晰展示
- 状态变化及时通知
- 支持快速筛选查找
8.3 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 金额显示异常 | 精度问题 | 使用toStringAsFixed |
| 状态未更新 | setState遗漏 | 检查状态更新 |
| 筛选无效 | 条件判断错误 | 检查筛选逻辑 |
| 表单验证失败 | validator错误 | 检查验证规则 |
九、运行说明
9.1 环境要求
| 环境 | 版本要求 |
|---|---|
| Flutter SDK | >= 3.0.0 |
| Dart SDK | >= 2.17.0 |
| 鸿蒙OS | API 21+ |
9.2 运行命令
# 查看可用设备
flutter devices
# 运行到鸿蒙设备
flutter run -d 127.0.0.1:5555 lib/main_expense_claim.dart
# 运行到Windows
flutter run -d windows -t lib/main_expense_claim.dart
# 代码分析
flutter analyze lib/main_expense_claim.dart
十、总结
报销管理应用通过清晰的功能设计,帮助企业规范报销流程。应用支持差旅、餐饮、交通、办公等多种报销类型,费用明细详细记录,审批流程透明可追溯。待审批和已通过金额统计一目了然,方便管理者掌握费用情况。
审批功能支持通过和拒绝操作,拒绝时可填写原因,让申请人了解问题所在。筛选和搜索功能帮助用户快速定位报销记录,提高工作效率。费用明细管理支持添加、删除操作,金额自动计算,减少人工计算错误。
界面设计采用蓝色主题风格,状态标签使用不同颜色区分,视觉层次清晰。应用采用Material Design 3设计规范,遵循Flutter最佳实践,代码结构清晰,易于维护和扩展。
报销管理,规范流程,透明审批,高效办公!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)