新增静态演示界面
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||||
import 'package:patbond_flutter/data/demo_data.dart';
|
||||
import 'package:patbond_flutter/models/models.dart';
|
||||
import 'package:patbond_flutter/widgets/common.dart';
|
||||
|
||||
class ServicesPage extends StatefulWidget {
|
||||
const ServicesPage({
|
||||
required this.showPersonal,
|
||||
required this.locationWeather,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool showPersonal;
|
||||
final LocationWeather locationWeather;
|
||||
|
||||
@override
|
||||
State<ServicesPage> createState() => _ServicesPageState();
|
||||
}
|
||||
|
||||
class _ServicesPageState extends State<ServicesPage> {
|
||||
late bool personal;
|
||||
String query = '';
|
||||
String filter = '全部';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
personal = widget.showPersonal;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ServicesPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.showPersonal != widget.showPersonal) {
|
||||
personal = widget.showPersonal;
|
||||
}
|
||||
}
|
||||
|
||||
List<ServiceProviderModel> get providers {
|
||||
final keyword = query.trim().toLowerCase();
|
||||
var list = serviceProviders.where((provider) {
|
||||
final matchesType = personal
|
||||
? provider.kind == ProviderKind.personal
|
||||
: provider.kind != ProviderKind.personal;
|
||||
final matchesQuery =
|
||||
keyword.isEmpty ||
|
||||
provider.name.toLowerCase().contains(keyword) ||
|
||||
provider.description.toLowerCase().contains(keyword) ||
|
||||
provider.tags.any((tag) => tag.toLowerCase().contains(keyword));
|
||||
return matchesType && matchesQuery;
|
||||
}).toList();
|
||||
|
||||
if (!personal) {
|
||||
if (filter == '距离优先') {
|
||||
list.sort(
|
||||
(a, b) => double.parse(
|
||||
a.distance.replaceAll('km', ''),
|
||||
).compareTo(double.parse(b.distance.replaceAll('km', ''))),
|
||||
);
|
||||
} else if (filter == '24H急诊') {
|
||||
list = list.where((item) => item.tags.contains('24H急诊')).toList();
|
||||
} else if (filter == '美容洗护') {
|
||||
list = list
|
||||
.where((item) => item.kind == ProviderKind.grooming)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<void> book(ServiceProviderModel provider) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('确认预约'),
|
||||
content: Text('预约 ${provider.name}\n服务价格:¥${provider.price} 起'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('确认'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true && mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('已提交「${provider.name}」预约(演示)')));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 28),
|
||||
children: [
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
icon: Icon(Icons.apartment_outlined),
|
||||
label: Text('专业机构'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
icon: Icon(Icons.person_pin_circle_outlined),
|
||||
label: Text('个人服务'),
|
||||
),
|
||||
],
|
||||
selected: {personal},
|
||||
showSelectedIcon: false,
|
||||
onSelectionChanged: (value) => setState(() {
|
||||
personal = value.first;
|
||||
filter = '全部';
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.near_me_outlined,
|
||||
size: 17,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
'正在展示 ${widget.locationWeather.displayArea} 附近的服务',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
onChanged: (value) => setState(() => query = value),
|
||||
decoration: InputDecoration(
|
||||
hintText: personal ? '搜索遛狗、寄养或上门喂养…' : '搜索宠物医院、美容或服务…',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '筛选',
|
||||
onPressed: () => ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('可使用下方快捷筛选条件'))),
|
||||
icon: const Icon(Icons.tune),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!personal) ...[
|
||||
const SizedBox(height: 12),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: ['全部', '距离优先', '24H急诊', '美容洗护']
|
||||
.map(
|
||||
(value) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: Text(value),
|
||||
selected: filter == value,
|
||||
onSelected: (_) => setState(() => filter = value),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
if (providers.isEmpty)
|
||||
const EmptyState(message: '没有找到符合条件的服务')
|
||||
else
|
||||
...providers.map(
|
||||
(provider) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 14),
|
||||
child: _ServiceCard(
|
||||
provider: provider,
|
||||
onBook: () => book(provider),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ServiceCard extends StatelessWidget {
|
||||
const _ServiceCard({required this.provider, required this.onBook});
|
||||
|
||||
final ServiceProviderModel provider;
|
||||
final VoidCallback onBook;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: RemoteImage(url: provider.image),
|
||||
),
|
||||
if (provider.verified)
|
||||
const Positioned(
|
||||
left: 12,
|
||||
top: 12,
|
||||
child: TagPill('认证服务', color: AppColors.success),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 10,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(235),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 9,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Text(
|
||||
'⭐ ${provider.rating}',
|
||||
style: const TextStyle(fontWeight: FontWeight.w800),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
provider.distance,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(provider.description),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 7,
|
||||
runSpacing: 7,
|
||||
children: provider.tags.map(TagPill.new).toList(),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'¥${provider.price}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const Text(' 起', style: TextStyle(color: AppColors.muted)),
|
||||
const Spacer(),
|
||||
FilledButton.icon(
|
||||
onPressed: onBook,
|
||||
icon: const Icon(Icons.calendar_month_outlined, size: 18),
|
||||
label: const Text('立即预约'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user