Files
andyesfly 56830e94ec feat: 完成智能电网电工端 Flutter 应用初始化
创建完整的 Flutter 项目结构,包含 Android 和 iOS 平台配置、核心网络层、路由、状态管理及业务模块(订单、收益、认证),适配原 uni-app 项目功能逻辑
2026-10-06 11:54:10 +08:00

159 lines
8.4 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/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../core/theme/app_colors.dart';
/// 图标组件,对应 uni-app 版页面里的 `<text class="iconfont icon-xxx">`。
///
/// **为什么是混合方案**:源工程只提供了 8 个真实字形(`iconfont.ttf`),而页面里
/// 引用的 `icon-lightning` / `icon-location` / `icon-star` 等**在字体里并不存在**,
/// 源工程实际渲染为空白。为保证复刻后的界面可读可用,这里:
/// - 8 个真实字形继续用搬运过来的 `iconfont.ttf` 渲染,保持逐像素一致;
/// - 其余语义图标改用 SVG 路径绘制,线条风格与源设计语言保持一致。
class XIcon extends StatelessWidget {
const XIcon(
this.name, {
super.key,
this.size = 20,
this.color = AppColors.textMuted,
});
final String name;
final double size;
final Color color;
/// `iconfont.ttf` 中真实存在的字形 -> Unicode 码点(取自源 `iconfont.css`)。
static const Map<String, int> glyphs = {
'shandian': 0xe6a4,
'tongzhi': 0xe60a,
'qianbao': 0xe60b,
'yaoqing': 0x100a5,
'dingdan': 0xe62a,
'weixin': 0xe665,
'wode': 0xe607,
'shouyi': 0x10186,
};
/// 语义图标的 SVG 路径(24×24 视图框,`currentColor` 会被替换成实际色值)。
static const Map<String, String> paths = {
'bolt': '<path d="M13 2L4.5 13.5H11l-2 8.5L19 10h-6.5z"/>',
'location':
'<path d="M12 2C8.1 2 5 5.1 5 9c0 5.2 7 13 7 13s7-7.8 7-13c0-3.9-3.1-7-7-7z"/><circle cx="12" cy="9" r="2.5"/>',
'clock':
'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>',
'filter': '<path d="M4 5h16l-7 8v5l-2 1v-6z"/>',
'map':
'<path d="M9 4L3 7v13l6-3 6 3 6-3V4l-6 3z"/><path d="M9 4v13M15 7v13" stroke="currentColor" stroke-width="1.5" fill="none"/>',
'refresh':
'<path d="M20 11a8 8 0 1 0-1.5 5.6" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M20 5v6h-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'check':
'<path d="M4 12.5l5 5L20 6.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'forward':
'<path d="M9 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'back':
'<path d="M15 5l-7 7 7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'arrow-down':
'<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'star': '<path d="M12 3l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.8 6.2 20.9l1.1-6.5L2.6 9.8l6.5-.9z"/>',
'verified':
'<path d="M12 2l2.5 2.5 3.5-.5 1 3.4 3 2-1 3.6 1 3.6-3 2-1 3.4-3.5-.5L12 22l-3.5-1-3.5.5-.5-3-3-2 1-4-.5-3H3l3-2 1-3.4 3.5.5z"/><path d="M8 12l3 3 5-6" stroke="#ffffff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'notification':
'<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 16v-5.5c0-3.1-1.6-5.6-4.5-6.3V3.5a1.5 1.5 0 0 0-3 0v.7C7.6 4.9 6 7.4 6 10.5V16l-2 2v1h16v-1l-2-2z"/>',
'share':
'<path d="M18 16.1c-.8 0-1.5.3-2 .8l-7.1-4.1c.1-.3.1-.5.1-.8s0-.5-.1-.8l7-4.1c.5.5 1.3.8 2.1.8 1.7 0 3-1.3 3-3s-1.3-3-3-3-3 1.3-3 3c0 .3 0 .5.1.8L8.1 9.8C7.6 9.3 6.9 9 6 9c-1.7 0-3 1.3-3 3s1.3 3 3 3c.9 0 1.6-.3 2.1-.8l7.1 4.1c-.1.2-.1.5-.1.7 0 1.6 1.3 3 3 3s3-1.3 3-3-1.4-2.9-3.1-2.9z"/>',
'navigation':
'<path d="M12 2l10 10-10 10L2 12z"/><path d="M12 8.6l3.4 3.4-3.4 3.4" stroke="#ffffff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'call':
'<path d="M6.6 3c.6 0 1.2.4 1.4 1l1 3c.2.5.1 1.1-.3 1.5L7 9.6c1 1.9 2.5 3.4 4.4 4.4l1.1-1.7c.4-.4 1-.5 1.5-.3l3 1c.6.2 1 .8 1 1.4V18c0 1-1 2-2 2C8 20 4 16 4 5c0-1 1-2 2-2z"/>',
'add-photo':
'<path d="M4 8h3l1.5-2h5L15 8h3a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2z"/><circle cx="10" cy="13.5" r="3"/><path d="M19 2v5M16.5 4.5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>',
'info':
'<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M12 11v6M12 7.4v.2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>',
'warning': '<path d="M12 3L2 20h20zM12 9v5M12 17.5v.1"/>',
'wallet':
'<rect x="3" y="6" width="18" height="13" rx="2.5"/><path d="M3 10.5h18" stroke="#ffffff" stroke-width="1.6" fill="none"/><circle cx="16.6" cy="14.4" r="1.3" fill="#ffffff"/>',
'list':
'<rect x="4" y="3" width="16" height="18" rx="2.5"/><path d="M8 8h8M8 12h8M8 16h5" stroke="#ffffff" stroke-width="1.8" stroke-linecap="round" fill="none"/>',
'settings':
'<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></g>',
'trend-up':
'<path d="M4 17l5-5 3 3 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M13 9h5v5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
'close':
'<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>',
'person': '<circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.5-6.5 8-6.5s8 2.5 8 6.5v1H4z"/>',
'camera':
'<rect x="3" y="6" width="18" height="14" rx="2"/><path d="M8 6l1.5-2h5L16 6"/><circle cx="12" cy="13" r="3.5"/>',
};
/// 源里两种命名风格混用(uni-app 的 `icon-xxx` 与 Material Symbols 名),
/// 这里统一做别名收敛,调用方两种写法都能渲染。
static const Map<String, String> _aliases = {
'lightning': 'bolt',
'time': 'clock',
'cert': 'verified',
'notify': 'notification',
'arrow-right': 'forward',
'order': 'list',
'earnings': 'wallet',
'chevron_left': 'back',
'location_on': 'location',
'directions': 'navigation',
'add_a_photo': 'add-photo',
};
static final Map<String, String> _svgCache = {};
static String _hex(Color color) =>
'#${(color.toARGB32() & 0xFFFFFF).toRadixString(16).padLeft(6, '0')}';
/// 归一化后的语义名:别名 -> 目标名。
static String resolve(String name) => _aliases[name] ?? name;
/// 生成 (图标, 颜色) 组合对应的 SVG 字符串,带缓存避免重复拼接。
static String svgFor(String name, Color color) {
final key = '$name|${color.toARGB32()}';
return _svgCache.putIfAbsent(key, () {
final body = (paths[resolve(name)] ?? paths['close']!).replaceAll(
'currentColor',
_hex(color),
);
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" '
'fill="${_hex(color)}">$body</svg>';
});
}
@override
Widget build(BuildContext context) {
final glyph = glyphs[name];
if (glyph != null) {
// 源里的彩色字形:直接交给 iconfont.ttf 渲染,保留原字形配色。
return SizedBox(
width: size,
height: size,
child: Center(
child: Text(
String.fromCharCode(glyph),
style: TextStyle(
fontFamily: 'iconfont',
fontSize: size,
height: 1,
color: color,
),
),
),
);
}
return SizedBox(
width: size,
height: size,
child: SvgPicture.string(
svgFor(name, color),
width: size,
height: size,
fit: BoxFit.contain,
),
);
}
}