feat(report): 实现AI学习报告计算页及相关组件
1. 新增报告计算进度圆环、科目卡片、完成弹窗组件 2. 重构report_computation_page,替换旧的自定义组件为新封装组件 3. 新增student_info_page的主题资源和UI重构,适配Flutter屏幕适配 4. 新增临时调试入口用于开发测试
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:new_learn/core/utils/img_url.dart';
|
||||
import 'package:new_learn/core/utils/screen_adapt_template.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【ComputationFinishedPopup】报告生成完成弹窗
|
||||
/// —— 对标 Vue finishedPopup.vue
|
||||
///
|
||||
/// 黑色 60% 遮罩 + 顶部 62% 屏高的 top-light 光效图 +
|
||||
/// 640x570rpx content-bg 内容图,底部「立即开启学习蝶变」渐变按钮,
|
||||
/// 入场 0.5s 淡入上移(popup-fade-up)。
|
||||
/// ============================================================
|
||||
class ComputationFinishedPopup extends StatefulWidget {
|
||||
const ComputationFinishedPopup({super.key, required this.onStart});
|
||||
|
||||
final VoidCallback onStart;
|
||||
|
||||
static const String topLightImg =
|
||||
'/StaticResource/2026-06-11/top-light1781173654372.png';
|
||||
static const String topLightPadImg =
|
||||
'/StaticResource/2026-06-24/top-light_pad1782270110001.png';
|
||||
static const String contentBgImg =
|
||||
'/StaticResource/2026-06-11/content-bg1781173794633.png';
|
||||
static const String starImg =
|
||||
'/StaticResource/2026-06-10/star1781089096898.png';
|
||||
|
||||
@override
|
||||
State<ComputationFinishedPopup> createState() =>
|
||||
_ComputationFinishedPopupState();
|
||||
}
|
||||
|
||||
class _ComputationFinishedPopupState extends State<ComputationFinishedPopup>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
)..forward();
|
||||
|
||||
late final Animation<double> _opacity = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
late final Animation<Offset> _offset =
|
||||
Tween(begin: const Offset(0, 0.08), end: Offset.zero).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.sizeOf(context).height;
|
||||
final isPad =
|
||||
MediaQuery.sizeOf(context).width >= AppAdapt.tabletBreakpoint;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// popup-mask
|
||||
const Positioned.fill(child: ColoredBox(color: Color(0x99000000))),
|
||||
// top-light:宽铺满,高 62%,居顶
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: screenHeight * 0.62,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(
|
||||
isPad
|
||||
? ComputationFinishedPopup.topLightPadImg
|
||||
: ComputationFinishedPopup.topLightImg,
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
errorWidget: (_, _, _) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
// popup-content:640x570rpx
|
||||
Center(
|
||||
child: FadeTransition(
|
||||
opacity: _opacity,
|
||||
child: SlideTransition(
|
||||
position: _offset,
|
||||
child: SizedBox(
|
||||
width: 320.w,
|
||||
height: 310.h,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
ImgUrl.build(ComputationFinishedPopup.contentBgImg),
|
||||
fit: BoxFit.fill,
|
||||
errorWidget: (_, _, _) => DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// gogo-button:90% 宽,距底 50rpx
|
||||
Positioned(
|
||||
left: 16.w,
|
||||
right: 16.w,
|
||||
bottom: 25.h,
|
||||
child: _buildStartButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStartButton() {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onStart,
|
||||
child: Container(
|
||||
height: 50.h, // 100rpx
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
// start-button-bg 多层 radial-gradient 用线性渐变近似
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF6AA2FF), Color(0xFF3D7DF5)],
|
||||
),
|
||||
border: Border.all(color: const Color(0x4DFFFFFF), width: 1.w),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x3D3D7DF5),
|
||||
blurRadius: 20,
|
||||
offset: Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(ComputationFinishedPopup.starImg),
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
errorWidget: (_, _, _) => SizedBox(width: 24.w, height: 24.w),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Text(
|
||||
'立即开启学习蝶变',
|
||||
style: TextStyle(
|
||||
fontSize: 16.spMin,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user