Flutter 实战:date_calculator 日期计算器的日期选择、间隔拆解与 鸿蒙 适配解析
Flutter 实战:date_calculator 日期计算器的日期选择、间隔拆解与 鸿蒙适配解析
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
日期计算器是一个非常适合练习 Flutter 交互状态的项目:它既有日期选择器,又有时间差计算;既要展示天数、周数、月数、年份差,又要支持基于起始日期增加天数得到结果日期。date_calculator 用一个单页应用覆盖了这些能力,代码结构清晰,适合用来分析 Flutter 小工具应用 的完整实现路径。
这篇文章会围绕 date_calculator 的真实实现展开,重点讲清楚:
DateTime状态如何初始化和更新。showDatePicker如何接入页面交互。- 天、周、月、年四类间隔如何计算。
- Add Days 模式为什么使用
StatefulBuilder。 - 这类纯 Flutter 工具应用迁移到 OpenHarmony 时要关注哪些点。
日期工具看似只是“两个日期相减”,但真正落到应用里,还要处理日期选择、结果表达、月份修正、局部输入、跨端弹窗和布局稳定性。

图示说明:上图展示 Flutter 页面在移动端的布局组织方式。date_calculator 的页面由模式切换区、日期选择卡片、结果卡片和间隔拆解区域组成。
一、项目定位与功能边界
1.1 应用定位
date_calculator 是一个日期计算工具,主要解决两类问题:
- 两个日期之间相差多少天。
- 从某个起始日期增加指定天数后是哪一天。
这类工具常见于排期、学习计划、项目周期、账期、活动周期和倒计时计算等场景。
1.2 功能模块
| 功能模块 | 页面表现 | 源码实现 |
|---|---|---|
| 日期间隔模式 | Days Between 标签 |
_selectedMode == 0 |
| 加天数模式 | Add Days 标签 |
_selectedMode == 1 |
| 起始日期 | Start Date 选择器 |
_startDate |
| 结束日期 | End Date 选择器 |
_endDate |
| 日期选择弹窗 | 系统样式日期选择器 | showDatePicker |
| 间隔拆解 | Days、Weeks、Months、Years | 多个 getter |
| 结果日期 | 输入天数后显示新日期 | _startDate.add(Duration(days: n)) |
1.3 技术栈
| 技术 | 使用位置 | 价值 |
|---|---|---|
| Flutter | 页面、卡片、输入、日期选择 | 快速搭建跨端 UI |
| Dart | 日期运算与状态逻辑 | DateTime、Duration、getter 简洁表达业务 |
| Material 3 | 主题与组件风格 | useMaterial3: true |
| StatefulWidget | 页面状态管理 | 日期、模式、输入变化都需要刷新 |
| StatefulBuilder | Add Days 局部状态 | 在局部区域内刷新输入结果 |
二、工程结构与运行环境
2.1 目录结构
date_calculator 保持了标准 Flutter 工程结构,主逻辑集中在 lib/main.dart。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、首页状态、日期算法和 UI 构建 |
pubspec.yaml |
依赖声明、Flutter 资源配置 |
test/widget_test.dart |
Widget 测试入口 |
ohos/ |
OpenHarmony 平台工程 |
analysis_options.yaml |
Dart 静态分析规则 |
2.2 基础运行命令
flutter doctor
flutter pub get
flutter run
这三个命令分别用于检查 Flutter 环境、恢复依赖和启动应用。当前项目依赖较轻,主要依赖 Flutter SDK 自带能力。
2.3 依赖声明
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
这个项目没有使用复杂原生插件,日期计算也不依赖网络服务,因此它的跨端验证重点主要集中在 UI、日期选择器、输入行为和窗口尺寸上。
三、应用入口与主题配置
3.1 main 函数
Flutter 应用从 main() 进入,随后加载根组件。
import 'package:flutter/material.dart';
void main() {
runApp(const DateCalculatorApp());
}
入口函数没有业务逻辑,只负责启动应用。这样可以让根组件承担应用配置,让首页承担交互逻辑。
3.2 根组件
class DateCalculatorApp extends StatelessWidget {
const DateCalculatorApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Date Calculator',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
useMaterial3: true,
),
home: const DateCalculatorHomePage(title: 'Date Calculator'),
);
}
}
根组件使用 StatelessWidget,因为它不保存日期、模式或输入状态。它的职责是配置应用标题、主题和首页。
3.3 主题选择
date_calculator 使用棕色作为种子色:
colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown)
棕色主题会影响 AppBar、模式选中态、结果卡片和统计项颜色,使页面视觉保持一致。
四、StatefulWidget 与核心状态
4.1 首页组件
class DateCalculatorHomePage extends StatefulWidget {
const DateCalculatorHomePage({super.key, required this.title});
final String title;
State<DateCalculatorHomePage> createState() =>
_DateCalculatorHomePageState();
}
首页之所以使用 StatefulWidget,是因为日期选择、模式切换和加天数输入都会改变页面显示。
4.2 状态字段
class _DateCalculatorHomePageState extends State<DateCalculatorHomePage> {
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
DateTime _endDate = DateTime.now();
int _selectedMode = 0;
}
这三个字段决定了页面的主要状态。
| 字段 | 类型 | 作用 |
|---|---|---|
_startDate |
DateTime |
起始日期,默认当前日期前 30 天 |
_endDate |
DateTime |
结束日期,默认当前日期 |
_selectedMode |
int |
当前模式,0 表示间隔计算,1 表示加天数 |
4.3 默认日期的意义
默认把起始日期设置为当前日期前 30 天,结束日期设置为当前日期,应用一打开就能展示一个明确结果,而不是空白页面。
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
DateTime _endDate = DateTime.now();
这种默认值设计对工具类应用很友好:用户进入页面后可以先理解结果,再根据需要调整日期。
五、日期间隔计算逻辑
5.1 天数差
int get _daysBetween => _endDate.difference(_startDate).inDays.abs();
difference 返回两个日期之间的 Duration,inDays 取天数,abs() 保证结果为正数。这样无论用户先选较早日期还是较晚日期,页面都展示正向间隔。
5.2 周数差
int get _weeksBetween => (_daysBetween / 7).floor();
周数差由天数差除以 7 后向下取整,表示完整周数。
| 天数差 | 完整周数 |
|---|---|
| 6 | 0 |
| 7 | 1 |
| 15 | 2 |
| 30 | 4 |
5.3 年份差
int get _yearsBetween => (_endDate.year - _startDate.year);
年份差直接取两个日期的年份字段相减。这里要注意,当前源码没有对月份和日期做周年修正,因此它表达的是年份字段差,而不是严格意义上的完整周年数。
日期计算要区分“字段差”和“完整周期差”。前者实现简单,后者通常需要更多边界规则。
六、月份差修正算法
6.1 月份差计算方法
int _getMonthsDifference() {
int months = (_endDate.year - _startDate.year) * 12;
months += _endDate.month - _startDate.month;
if (_endDate.day < _startDate.day) {
months--;
}
return months.abs();
}
月份差比天数差复杂,因为不同月份天数不同,不能简单用天数除以 30。
6.2 算法步骤
- 先用年份差乘以 12,得到跨年的基础月份。
- 再加上结束月份与起始月份的差。
- 如果结束日小于起始日,说明最后一个月还没有走满,扣减一个月。
- 最后使用
abs()保证展示正数。
6.3 示例表
| 起始日期 | 结束日期 | 计算说明 | 月份差 |
|---|---|---|---|
| Jan 10, 2026 | Feb 10, 2026 | 正好满一个月 | 1 |
| Jan 15, 2026 | Feb 10, 2026 | 结束日小于起始日 | 0 |
| Jan 01, 2026 | Mar 01, 2026 | 满两个月 | 2 |
| Nov 20, 2025 | Feb 19, 2026 | 最后一个月未满 | 2 |
这个算法适合轻量展示“整月差”,可读性强,计算成本低。
七、日期选择器接入
7.1 showDatePicker 调用
项目通过 _selectDate 打开日期选择器。
Future<void> _selectDate(bool isStart) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: isStart ? _startDate : _endDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
if (isStart) {
_startDate = picked;
} else {
_endDate = picked;
}
});
}
}
showDatePicker 是 Flutter Material 提供的日期选择弹窗,返回值可能为空。用户取消选择时,picked 为 null。
7.2 参数设计
| 参数 | 当前取值 | 含义 |
|---|---|---|
context |
当前页面上下文 | 用于展示弹窗 |
initialDate |
起始或结束日期 | 打开弹窗时默认选中 |
firstDate |
DateTime(1900) |
可选最早日期 |
lastDate |
DateTime(2100) |
可选最晚日期 |
7.3 写入状态
isStart 决定更新哪个日期。
if (isStart) {
_startDate = picked;
} else {
_endDate = picked;
}
这让起始日期选择器和结束日期选择器复用同一个方法,减少重复逻辑。
八、日期格式化方法
8.1 月份缩写数组
源码使用英文月份缩写数组格式化日期。
String _formatDate(DateTime date) {
final months = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
return '${months[date.month - 1]} ${date.day}, ${date.year}';
}
格式化后的结果类似 Jun 10, 2026。
8.2 数组索引
DateTime.month 的取值范围是 1 到 12,而数组索引从 0 开始,因此需要 date.month - 1。
months[date.month - 1]
这是一处很小但很重要的细节。日期格式化一旦索引错误,就会出现月份偏移。
8.3 展示格式
| 日期对象 | 展示结果 |
|---|---|
DateTime(2026, 1, 1) |
Jan 1, 2026 |
DateTime(2026, 6, 10) |
Jun 10, 2026 |
DateTime(2026, 12, 31) |
Dec 31, 2026 |
当前实现适合英文界面。如果面向中文用户,可以结合本地化方案展示为 2026年6月10日。
九、页面布局结构
9.1 Scaffold 页面骨架
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 模式切换、日期选择、结果卡片、拆解卡片
],
),
),
);
页面主体使用 SingleChildScrollView,可以避免小屏幕或横屏情况下内容溢出。
9.2 模块顺序
页面从上到下依次是:
- 模式切换区。
- 起始日期与结束日期选择卡片。
- 结果卡片。
- 间隔拆解卡片。
这种顺序符合工具类应用的使用路径:先选择模式,再输入条件,最后查看结果。
9.3 布局组件表
| 组件 | 用途 |
|---|---|
Scaffold |
页面基础结构 |
AppBar |
顶部标题 |
SingleChildScrollView |
允许内容滚动 |
Column |
纵向排列模块 |
Card |
承载日期和结果区域 |
Row |
横向排列日期选择器 |
Expanded |
平分横向空间 |
十、模式切换实现
10.1 模式容器
Container(
decoration: BoxDecoration(
color: Colors.brown.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
_buildModeTab(0, 'Days Between', Icons.calendar_view_day),
_buildModeTab(1, 'Add Days', Icons.add_circle),
],
),
)
两个模式放在同一个容器中,视觉上形成分段控制器。
10.2 单个模式标签
Widget _buildModeTab(int mode, String label, IconData icon) {
final isSelected = _selectedMode == mode;
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedMode = mode),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isSelected ? Colors.brown : Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 18),
const SizedBox(width: 4),
Text(label),
],
),
),
),
);
}
GestureDetector 负责点击切换,isSelected 负责控制颜色和字重。
10.3 模式对 UI 的影响
| 模式 | 结果标题 | 主结果区域 | 额外区域 |
|---|---|---|---|
Days Between |
Time Difference |
天数差 | Breakdown 卡片 |
Add Days |
Result Date |
输入天数和结果日期 | 不展示 Breakdown |
这种条件渲染让一个页面复用两套功能,同时保持结构简单。
十一、日期选择卡片
11.1 日期选择区域
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: _buildDateSelector(
'Start Date',
_startDate,
() => _selectDate(true),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildDateSelector(
'End Date',
_endDate,
() => _selectDate(false),
),
),
],
),
),
)
两个日期选择器放在同一行,结构对称,便于比较。
11.2 选择器组件
Widget _buildDateSelector(
String label,
DateTime date,
VoidCallback onTap,
) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
const SizedBox(height: 4),
Text(_formatDate(date)),
],
),
),
);
}
这个组件把标签、日期文本和点击行为封装到一起,减少了 build() 方法里的重复代码。
11.3 交互路径
点击 Start Date
打开日期选择器
用户选择日期
picked 不为空
setState 更新 _startDate
getter 重新计算间隔
页面刷新
结束日期选择器的流程完全一致,只是写入 _endDate。
十二、结果卡片与间隔拆解
12.1 结果卡片
Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [Colors.brown.shade100, Colors.brown.shade50],
),
),
child: Column(
children: [
Text(_selectedMode == 0 ? 'Time Difference' : 'Result Date'),
],
),
),
)
结果卡片使用大圆角、阴影和渐变背景,让关键结果在页面中更醒目。
12.2 天数结果
if (_selectedMode == 0) ...[
Text(
'$_daysBetween',
style: const TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.brown,
),
),
const Text('days'),
]
日期间隔模式下,页面突出展示天数,因为天数是最直观、最常用的结果。
12.3 Breakdown 卡片
if (_selectedMode == 0)
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Breakdown'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildBreakdownItem('Days', _daysBetween.toString()),
_buildBreakdownItem('Weeks', _weeksBetween.toString()),
_buildBreakdownItem('Months', _monthsBetween.toString()),
_buildBreakdownItem('Years', _yearsBetween.toString()),
],
),
],
),
),
)
拆解卡片让用户同时看到天、周、月、年四种视角。
十三、Add Days 模式解析
13.1 局部输入实现
Add Days 模式由 _buildAddDaysResult() 负责。
Widget _buildAddDaysResult() {
final addController = TextEditingController();
return StatefulBuilder(
builder: (context, setState) {
int daysToAdd = int.tryParse(addController.text) ?? 0;
DateTime resultDate = _startDate.add(Duration(days: daysToAdd));
return Column(
children: [
TextField(
controller: addController,
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
daysToAdd = int.tryParse(value) ?? 0;
resultDate = _startDate.add(Duration(days: daysToAdd));
});
},
),
Text(_formatDate(resultDate)),
],
);
},
);
}
这段代码使用局部 StatefulBuilder 处理 Add Days 输入,而不是把输入控制器放到页面 State 顶层。
13.2 计算公式
DateTime resultDate = _startDate.add(Duration(days: daysToAdd));
公式非常直观:以起始日期为基准,增加输入的天数。
| Start Date | Days to add | Result Date |
|---|---|---|
| Jun 10, 2026 | 1 | Jun 11, 2026 |
| Jun 10, 2026 | 7 | Jun 17, 2026 |
| Jun 10, 2026 | 30 | Jul 10, 2026 |
13.3 局部状态的特点
StatefulBuilder 可以让局部区域重新构建,适合这种范围很小的临时输入。当前实现能正常表达 Add Days 的交互,但由于控制器在方法内部创建,复杂场景下可以进一步放到 State 字段中统一管理生命周期。
十四、边界输入与日期规则
14.1 日期选择范围
firstDate: DateTime(1900),
lastDate: DateTime(2100),
这个范围覆盖了大多数日常使用场景,也避免用户选择过于极端的日期。
14.2 输入天数解析
int daysToAdd = int.tryParse(addController.text) ?? 0;
输入为空或无法解析时回退为 0,因此结果日期会保持为起始日期。
14.3 日期顺序
天数差和月份差都使用了绝对值:
_endDate.difference(_startDate).inDays.abs();
这意味着即使用户把结束日期选在起始日期之前,页面也会展示正数间隔。
14.4 边界场景表
| 场景 | 当前表现 | 说明 |
|---|---|---|
| 起始日期早于结束日期 | 正常展示间隔 | 标准场景 |
| 起始日期晚于结束日期 | 展示正数间隔 | 使用 abs() |
| 取消日期选择 | 不更新日期 | picked == null |
| Add Days 输入为空 | 增加 0 天 | 回退为 0 |
| Add Days 输入非数字 | 增加 0 天 | tryParse 失败 |
十五、Widget 测试设计
15.1 基础渲染测试
import 'package:flutter_test/flutter_test.dart';
import '../lib/main.dart';
void main() {
testWidgets('date calculator renders home page', (tester) async {
await tester.pumpWidget(const DateCalculatorApp());
expect(find.text('Date Calculator'), findsWidgets);
expect(find.text('Days Between'), findsOneWidget);
expect(find.text('Add Days'), findsOneWidget);
});
}
这类测试能验证根组件、标题和模式切换区是否正常渲染。
15.2 日期选择器入口测试
testWidgets('date selectors are visible', (tester) async {
await tester.pumpWidget(const DateCalculatorApp());
expect(find.text('Start Date'), findsOneWidget);
expect(find.text('End Date'), findsOneWidget);
});
由于日期选择器属于弹窗交互,测试时可以先覆盖入口控件是否存在。
15.3 Add Days 模式测试
testWidgets('add days mode shows input field', (tester) async {
await tester.pumpWidget(const DateCalculatorApp());
await tester.tap(find.text('Add Days'));
await tester.pump();
expect(find.text('Days to add'), findsOneWidget);
expect(find.text('Result Date'), findsOneWidget);
});
这个测试覆盖模式切换后的条件渲染。
15.4 测试命令
flutter test
如果测试入口引用的根组件与当前源码不一致,Flutter 会在编译阶段直接暴露问题。保持测试代码和真实组件名称一致,是小项目持续维护的基础。
十六、OpenHarmony 适配观察
16.1 适配优势
date_calculator 的业务逻辑主要由 Dart 和 Flutter Widget 实现,没有复杂平台通道和原生能力依赖。
| 维度 | 当前项目情况 | OpenHarmony 关注点 |
|---|---|---|
| 日期算法 | Dart DateTime |
多端计算结果一致 |
| 日期选择器 | showDatePicker |
弹窗样式和交互验证 |
| 输入框 | TextField |
数字键盘和焦点行为 |
| 图标 | Material Icons | 字体资源显示 |
| 布局 | 标准 Widget | 小屏、横屏、窗口尺寸 |
16.2 构建命令参考
flutter clean
flutter pub get
flutter build hap
具体构建命令与所使用的 OpenHarmony Flutter 适配环境有关。对这个项目而言,业务层逻辑较独立,主要验证点集中在渲染和交互。
16.3 运行验证要点
- 应用能正常启动到首页。
Days Between与Add Days可以切换。- 起始日期和结束日期都能打开日期选择器。
- 日期选择后天数差、周数、月份差同步刷新。
- Add Days 输入后结果日期能即时变化。
- 小屏下日期选择卡片不出现明显溢出。
OpenHarmony 适配中,纯 Dart 计算通常不是主要风险,真正需要细看的往往是弹窗、输入法、字体和窗口尺寸。
十七、可维护性与演进方向
17.1 当前实现的优点
| 维度 | 表现 |
|---|---|
| 状态集中 | 起始日期、结束日期、模式都在 State 中 |
| 方法清晰 | 日期选择、格式化、月份计算分别封装 |
| 视觉直观 | 结果卡片和拆解卡片层级清楚 |
| 依赖简单 | 没有额外日期库或平台插件 |
17.2 日期算法抽离
如果项目继续扩展,可以把日期计算逻辑抽离为纯函数。
int daysBetween(DateTime start, DateTime end) {
return end.difference(start).inDays.abs();
}
int weeksBetween(DateTime start, DateTime end) {
return (daysBetween(start, end) / 7).floor();
}
DateTime addDays(DateTime start, int days) {
return start.add(Duration(days: days));
}
抽离后的函数可以单独做单元测试,页面只负责收集输入和展示结果。
17.3 模式枚举
当前 _selectedMode 使用 0 和 1 表示模式。随着功能增加,可以使用枚举提升可读性。
enum DateCalculatorMode {
daysBetween,
addDays,
}
枚举能减少魔法数字,让代码语义更稳定。
十八、常见问题与优化建议
18.1 为什么天数差使用绝对值
用户选择日期时不一定保证起始日期早于结束日期。使用 abs() 后,页面始终展示正数间隔,适合多数日常计算场景。
18.2 月份差为什么不能用天数除以 30
因为每个月天数不同,二月、闰年、大小月都会影响结果。当前源码按年份和月份字段计算,再根据日期字段修正,更接近“完整月数”的语义。
18.3 年份差是否等于完整周年数
当前 _yearsBetween 直接使用年份字段相减,没有判断月份和日期,因此它是年份字段差,不是严格完整周年数。文章分析时要按源码真实表现理解。
18.4 Add Days 为什么使用局部 StatefulBuilder
Add Days 的输入只影响结果卡片内部,不影响页面其他区域。StatefulBuilder 可以让局部输入和结果刷新保持在较小范围内。
18.5 为什么日期选择范围是 1900 到 2100
这个范围覆盖常见历史和未来日期需求,同时避免选择过于极端的日期。对日常排期、账期、纪念日和项目周期来说已经足够。
18.6 为什么不引入日期处理库
当前需求只涉及基础日期差、月份差和加天数,Dart 内置 DateTime 与 Duration 已经可以完成。引入额外库会增加依赖和跨端验证成本。
总结
date_calculator 展示了 Flutter 日期工具应用的一条清晰实现路径:使用 DateTime 保存起始日期和结束日期,使用 showDatePicker 完成日期选择,使用 getter 计算天数、周数、月份差和年份字段差,再通过结果卡片和 Breakdown 区域展示给用户。
从工程角度看,它的优势是依赖简单、状态集中、交互路径短,非常适合用于学习 Flutter 表单类、工具类页面的开发方式。对于 OpenHarmony 适配来说,这类项目没有复杂原生插件,关键是验证日期选择器、输入法、字体图标、滚动布局和不同屏幕尺寸下的展示效果。
如果继续扩展,可以把日期算法抽离为纯函数,为间隔计算和 Add Days 增加单元测试,并将模式编号升级为枚举。这样既保留单页工具的轻量感,也能让代码更容易长期维护。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐


所有评论(0)