flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
/// ============================================================
/// 【ImgUrl】图片地址工具 —— 对标 UniApp utils/image.js 的 $img
///
/// 规则(与原 $img 完全一致):
/// 1. 空值 / 纯空白 → 返回空串
/// 2. 以 http 开头 → 原样返回(已是完整 URL)
/// 3. 以 /static/ 开头 → 原样返回(小程序本地资源;Flutter 端此类
/// 资源应改用 Image.asset / Material Icon,不能直接加载网络)
/// 4. 其余 → 拼 CDN 域名返回完整 URL
/// ============================================================
class ImgUrl {
ImgUrl._();
/// 图片 CDN 域名(对标 image.js 的 IMG_URL;旧域名 hx.23544.com 已弃用)。
/// TODO(业务逻辑): 如需按环境切换,可迁移进 AppEnv 的 _envTable。
static const String _cdn = 'https://hx.cnzhyx.cn';
/// 等价 Vue 的 $img(path)
static String build(String? path) {
if (path == null) return '';
final s = path.trim();
if (s.isEmpty) return '';
if (s.startsWith('http')) return s;
if (s.startsWith('/static/')) return s;
return '$_cdn$s';
}
}
+132
View File
@@ -0,0 +1,132 @@
import 'dart:math' show min;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// ============================================================
/// 【新项目屏幕适配模板】= 方案 B(平板动态 designSize) + 方案 A(.spMin) + 兜底
///
/// 使用方法(新项目):
/// 1. pubspec.yaml 添加 flutter_screenutil: ^5.9.0;
/// 2. 复制本文件到新项目 lib/utils/,入口改成 runApp(const ScreenAdaptApp());
/// 3. 页面里统一按下面规则写尺寸:
/// 字号:fontSize: 16.spMin ← 大屏只缩不放(关键!)
/// 宽度/横向间距:16.w 高度/纵向间距:16.h 圆角:12.r
///
/// 一、这套方案解决什么问题?
/// 默认 .sp = 字号 × 屏宽/375。平板宽 800dp → 字体放大 2.1 倍,爆炸。
/// 方案 B:平板(≥600dp)自动切换大 designSize,缩放比例回到 ~1;
/// 方案 A:.spMin = min(设计值, .sp),字体只允许缩小,永不超设计稿;
/// 兜底:fontSizeResolver 限制字体最大放大 1.2 倍(防横屏/极端宽屏)。
///
/// 二、注意事项:
/// 1. 平板上布局尺寸接近设计稿原值,若想"大屏显示更多内容",
/// 需配合 LayoutBuilder / 断点做响应式布局;
/// 2. 所有阈值集中在 AppAdapt,一处改、全局生效;
/// 3. designSize 用 MediaQueryData.fromView 取物理屏尺寸,
/// 不依赖 MaterialApp,启动时即可正确计算。
/// ============================================================
/// ---------- 工具:全局适配配置(一处修改,全局生效) ----------
class AppAdapt {
AppAdapt._();
/// 平板判定断点(Material 规范:宽度 ≥ 600dp 视为平板/大屏)
static const double tabletBreakpoint = 600;
/// 手机设计稿(按你的 UI 设计稿尺寸改)
static const Size phoneDesign = Size(375, 812);
/// 字体最大放大倍数兜底(防横屏/极端宽屏字体失控)
static const double maxFontScale = 1.2;
/// 方案 B 核心:根据屏幕尺寸选择设计稿
///
/// 手机:用 375x812,按宽度比例缩放;
/// 平板:设计稿 = 设备自身逻辑尺寸 → scaleWidth 恒为 1,
/// 横竖屏 UI 尺寸完全一致,不会旋转后变小/变大。
static Size designSizeOf(BuildContext context) {
final mq = MediaQuery.maybeOf(context) ??
MediaQueryData.fromView(View.of(context));
final size = mq.size;
// 平板判定用"宽高都 ≥600":避免手机横屏(如 932x430)被误判成平板
final isTablet =
size.width >= tabletBreakpoint && size.height >= tabletBreakpoint;
return isTablet ? size : phoneDesign;
}
/// 方案 A 兜底:自定义字体缩放,放大倍数封顶 maxFontScale
static double fontSizeResolver(num fontSize, ScreenUtil instance) {
final scaled = fontSize * instance.scaleText;
return min(scaled, fontSize * maxFontScale);
}
}
/// ---------- 入口模板(新项目直接 runApp 这个) ----------
class ScreenAdaptApp extends StatelessWidget {
const ScreenAdaptApp({super.key});
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: AppAdapt.designSizeOf(context), // 方案 B:平板/手机自动切换
minTextAdapt: true,
fontSizeResolver: AppAdapt.fontSizeResolver, // 方案 A 兜底
builder: (context, child) {
return MaterialApp(
title: '新项目',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: const Color(0xFF3B82F6),
scaffoldBackgroundColor: const Color(0xFFF5F7FA),
),
home: const _TemplateHome(),
);
},
);
}
}
/// 模板首页:演示 .spMin / .w / .h / .r 的写法
class _TemplateHome extends StatelessWidget {
const _TemplateHome();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('适配模板')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 关键:字号用 .spMin,平板上不会爆炸
Text(
'标题 24.spMin',
style: TextStyle(fontSize: 24.spMin, fontWeight: FontWeight.bold),
),
SizedBox(height: 12.h),
Text(
'正文 16.spMin',
style: TextStyle(fontSize: 16.spMin, color: Colors.grey),
),
SizedBox(height: 24.h),
Container(
width: 200.w,
height: 60.h,
decoration: BoxDecoration(
color: const Color(0xFF3B82F6),
borderRadius: BorderRadius.circular(12.r),
),
alignment: Alignment.center,
child: const Text(
'按钮 200.w x 60.h',
style: TextStyle(color: Colors.white),
),
),
],
),
),
);
}
}