Files
CloudHome-Ops/lib/widgets/store_theme_grid.dart

147 lines
4.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../models/theme_template.dart';
/// 主题模板网格卡片(封面图 + 名称)。
///
/// 用于门店表单中「主题皮肤」选择;[selectedId] 高亮选中态,
/// [onSelected] 回传选中模板的 [ThemeTemplate.id]。
class StoreThemeGrid extends StatelessWidget {
const StoreThemeGrid({
super.key,
required this.templates,
required this.onSelected,
this.selectedId,
});
/// 可选主题模板列表。
final List<ThemeTemplate> templates;
/// 选中回调,回传模板 id。
final ValueChanged<String> onSelected;
/// 当前选中的模板 id(高亮)。
final String? selectedId;
@override
Widget build(BuildContext context) {
if (templates.isEmpty) {
return const SizedBox(
height: 80,
child: Center(child: Text('暂无可用主题模板')),
);
}
return GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.9,
children: templates.map((template) {
final selected = template.id == selectedId;
return _ThemeCard(
template: template,
selected: selected,
onTap: () => onSelected(template.id),
);
}).toList(),
);
}
}
class _ThemeCard extends StatelessWidget {
const _ThemeCard({
required this.template,
required this.selected,
required this.onTap,
});
final ThemeTemplate template;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final primary = _accentColor(template.name);
final coverUrl = template.coverUrl;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: selected
? Theme.of(context).colorScheme.primary
: Colors.transparent,
width: selected ? 2.5 : 1,
),
),
child: Column(
children: [
// 封面预览(优先 coverUrl,否则用名称哈希色块)。
Expanded(
child: Container(
decoration: BoxDecoration(
color: coverUrl == null || coverUrl.isEmpty ? primary : null,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(10)),
image: coverUrl != null && coverUrl.isNotEmpty
? DecorationImage(
image: NetworkImage(coverUrl),
fit: BoxFit.cover,
)
: null,
),
alignment: Alignment.center,
child: coverUrl == null || coverUrl.isEmpty
? Text(
template.name.isEmpty ? '主题' : template.name[0],
style: const TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
),
)
: null,
),
),
// 名称。
Container(
height: 34,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
template.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
),
),
],
),
),
);
}
static const _palette = [
Color(0xFF3B82F6),
Color(0xFF8B5CF6),
Color(0xFFEC4899),
Color(0xFFF59E0B),
Color(0xFF10B981),
Color(0xFF6366F1),
];
static Color _accentColor(String name) {
var hash = 0;
for (var i = 0; i < name.length; i++) {
hash = (hash + name.codeUnitAt(i) * (i + 1)) % _palette.length;
}
return _palette[hash];
}
}