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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
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';
|
||||
|
||||
/// ============================================================
|
||||
/// 【ComputationProgressRing】AI 计算圆环 —— 对标 Vue ProgressRing.vue
|
||||
///
|
||||
/// 由外到内:
|
||||
/// 1. 虚线刻度图(700rpx,6s 顺时针慢转)
|
||||
/// 2. 外圈光环球图(524rpx,1.5s 反向快转)
|
||||
/// 3. 外层渐变光圈 ellipse-wai(452rpx)
|
||||
/// 4. 渐变进度弧(370rpx,线宽 20rpx,#8BFFF5 → #006CFF 纵向渐变)
|
||||
/// 5. 白色内圆 ellipse-nei(300rpx)+ 渐变百分比文字 + 副标题
|
||||
///
|
||||
/// Vue 用 canvas 画弧;Flutter 用 CustomPaint,渐变方向与 Vue
|
||||
/// createLinearGradient(center,0 → center,size) 保持一致(纵向)。
|
||||
/// ============================================================
|
||||
class ComputationProgressRing extends StatefulWidget {
|
||||
const ComputationProgressRing({super.key, required this.progress});
|
||||
|
||||
/// 进度值 0-100
|
||||
final double progress;
|
||||
|
||||
/// 外圈光环球图(Vue props.outerImg 默认值)
|
||||
static const String outerImg =
|
||||
'/StaticResource/2026-06-11/outer1781143064766.png';
|
||||
|
||||
/// 虚线刻度图(Vue props.dashedLineImg 默认值)
|
||||
static const String dashedImg =
|
||||
'/StaticResource/2026-06-11/dashedLine1781143064766.png';
|
||||
|
||||
@override
|
||||
State<ComputationProgressRing> createState() =>
|
||||
_ComputationProgressRingState();
|
||||
}
|
||||
|
||||
class _ComputationProgressRingState extends State<ComputationProgressRing>
|
||||
with TickerProviderStateMixin {
|
||||
/// 外圈:Vue rotate-cw 0 → -360deg / 1.5s
|
||||
late final AnimationController _outerCtrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
)..repeat();
|
||||
|
||||
/// 虚线:Vue rotate-ccw 0 → 360deg / 6s
|
||||
late final AnimationController _dashedCtrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 6),
|
||||
)..repeat();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_outerCtrl.dispose();
|
||||
_dashedCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = 262.w; // 524rpx
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// 虚线刻度(700rpx,超出 524rpx 容器,允许溢出绘制)
|
||||
RepaintBoundary(
|
||||
child: RotationTransition(
|
||||
turns: _dashedCtrl,
|
||||
child: _ringImage(ComputationProgressRing.dashedImg, 350.w),
|
||||
),
|
||||
),
|
||||
// 外圈光球
|
||||
RepaintBoundary(
|
||||
child: RotationTransition(
|
||||
turns: ReverseAnimation(_outerCtrl),
|
||||
child: _ringImage(ComputationProgressRing.outerImg, 262.w),
|
||||
),
|
||||
),
|
||||
// ellipse-wai:87.42deg 紫色渐变 + 内发光(内阴影用外发光近似)
|
||||
Container(
|
||||
width: 226.w,
|
||||
height: 226.w,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
stops: [0.1133, 1.0],
|
||||
colors: [Color(0x6B5B5DFF), Color(0x0000FFF2)],
|
||||
),
|
||||
border: Border.all(color: const Color(0x33FFFFFF), width: 1.w),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Color(0x33FFFFFF), blurRadius: 25),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 进度弧(canvas)
|
||||
SizedBox(
|
||||
width: 185.w,
|
||||
height: 185.w,
|
||||
child: CustomPaint(painter: _RingArcPainter(widget.progress)),
|
||||
),
|
||||
// ellipse-nei:内圆
|
||||
Container(
|
||||
width: 150.w,
|
||||
height: 150.w,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment(-0.6, -0.8),
|
||||
end: Alignment(0.87, 0.49),
|
||||
stops: [0.42, 0.82],
|
||||
colors: [Color(0xFFB1DFFF), Color(0xFFFFFFFF)],
|
||||
),
|
||||
border: Border.all(color: const Color(0x80FFFFFF)),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x0D1D2B47),
|
||||
blurRadius: 28,
|
||||
offset: Offset(0, 14),
|
||||
),
|
||||
BoxShadow(color: Color(0x80FFFFFF), blurRadius: 26),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
_gradientText(
|
||||
widget.progress.floor().toString(),
|
||||
40.spMin,
|
||||
height: 1.0,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 2.w, bottom: 6.h),
|
||||
child: _gradientText('%', 14.5.spMin),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
'AI加载中',
|
||||
style: TextStyle(
|
||||
fontSize: 14.5.spMin,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xFF0F9AFF),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _ringImage(String path, double size) {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(path),
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.contain,
|
||||
errorWidget: (_, _, _) => SizedBox(width: size, height: size),
|
||||
);
|
||||
}
|
||||
|
||||
/// 渐变文字:linear-gradient(180deg, #006cff → #529bff)
|
||||
Widget _gradientText(String text, double fontSize, {double? height}) {
|
||||
return ShaderMask(
|
||||
shaderCallback: (rect) => ui.Gradient.linear(
|
||||
rect.topCenter,
|
||||
rect.bottomCenter,
|
||||
const [Color(0xFF006CFF), Color(0xFF529BFF)],
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
height: height,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 进度弧画笔(对标 Vue canvas arc + 纵向线性渐变 + round 线帽)
|
||||
class _RingArcPainter extends CustomPainter {
|
||||
_RingArcPainter(this.progress);
|
||||
|
||||
final double progress;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final p = progress.clamp(0.0, 100.0);
|
||||
if (p <= 0) return;
|
||||
|
||||
final stroke = 10.w; // 20rpx
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final radius = (size.width - stroke) / 2;
|
||||
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..shader = ui.Gradient.linear(
|
||||
Offset(center.dx, center.dy - radius),
|
||||
Offset(center.dx, center.dy + radius),
|
||||
const [Color(0xFF8BFFF5), Color(0xFF006CFF)],
|
||||
);
|
||||
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: center, radius: radius),
|
||||
-math.pi / 2, // 从 12 点方向开始
|
||||
2 * math.pi * p / 100,
|
||||
false,
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RingArcPainter oldDelegate) =>
|
||||
oldDelegate.progress != progress;
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
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';
|
||||
|
||||
/// 科目计算状态(对标 Vue SubjectStatus)
|
||||
enum ComputationSubjectStatus { notStarted, inProgress, finished }
|
||||
|
||||
/// ============================================================
|
||||
/// 【ComputationSubjectItem】AI 计算科目卡片
|
||||
/// —— 对标 reportComputation.vue 的 .subject-item
|
||||
///
|
||||
/// 右上角标(subscript):未开始/进行中蓝色闪电底,已完成绿色对勾底;
|
||||
/// 标题行:科目名 + 科目图标 + ai-plan「AI学习计划+」图;
|
||||
/// 知识点行 + 底部渐变进度条(进行中蓝 / 已完成绿)。
|
||||
/// ============================================================
|
||||
class ComputationSubjectItem extends StatelessWidget {
|
||||
const ComputationSubjectItem({
|
||||
super.key,
|
||||
required this.subjectName,
|
||||
required this.iconUrl,
|
||||
required this.knowCount,
|
||||
required this.progress,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
final String subjectName;
|
||||
final String iconUrl;
|
||||
final int knowCount;
|
||||
|
||||
/// 0-100
|
||||
final int progress;
|
||||
final ComputationSubjectStatus status;
|
||||
|
||||
// ---- 图片资源(default.json ai-plan + Vue 内联 $img 角标图)----
|
||||
static const String _aiPlanImg =
|
||||
'/StaticResource/2026-06-11/aiPlan1781157211012.png';
|
||||
static const String _subscriptBgBlue =
|
||||
'/StaticResource/2026-06-11/subscript-bg11781162055363.png';
|
||||
static const String _subscriptBgGreen =
|
||||
'/StaticResource/2026-06-11/subscript-bg21781162055358.png';
|
||||
static const String _iconFlash =
|
||||
'/StaticResource/2026-06-11/subscript-cion1-1781162401643.png';
|
||||
static const String _iconCheck =
|
||||
'/StaticResource/2026-06-11/subscript-cion2-1781162401644.png';
|
||||
|
||||
bool get _isFinished => status == ComputationSubjectStatus.finished;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Opacity(
|
||||
// Vue: 未开始 opacity 0.8
|
||||
opacity: status == ComputationSubjectStatus.notStarted ? 0.8 : 1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16.r), // 16rpx
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [0, 0.3, 1],
|
||||
colors: _isFinished
|
||||
? const [Color(0xFFE3FCF4), Color(0xFFF7FCFA), Colors.white]
|
||||
: const [Color(0xFFE3F7FC), Color(0xFFF7FBFC), Colors.white],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(14.w, 14.w, 14.w, 20.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitle(),
|
||||
SizedBox(height: 16.h), // 知识点行 margin 32rpx 0
|
||||
_buildKnowledge(),
|
||||
SizedBox(height: 16.h),
|
||||
_buildProgressBar(),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右上角标
|
||||
Positioned(top: 0, right: 0, child: _buildSubscript()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 标题行:科目名 + 科目图标 + AI学习计划图 =====
|
||||
Widget _buildTitle() {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
subjectName,
|
||||
style: TextStyle(
|
||||
fontSize: 18.spMin, // 36rpx
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1D2B47),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
if (iconUrl.isNotEmpty)
|
||||
CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(iconUrl),
|
||||
width: 26.w, // 52rpx,widthFix
|
||||
fit: BoxFit.fitWidth,
|
||||
errorWidget: (_, _, _) => Icon(
|
||||
Icons.menu_book,
|
||||
size: 26.w,
|
||||
color: const Color(0xFF3D7DF5),
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(Icons.menu_book, size: 26.w, color: const Color(0xFF3D7DF5)),
|
||||
SizedBox(width: 6.w),
|
||||
CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(_aiPlanImg),
|
||||
width: 77.w, // 154rpx,widthFix
|
||||
fit: BoxFit.fitWidth,
|
||||
errorWidget: (_, _, _) => const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 知识点行(Vue 预计时间已注释,只保留知识点)=====
|
||||
Widget _buildKnowledge() {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF3F8FE),
|
||||
borderRadius: BorderRadius.circular(4.r),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'知识点',
|
||||
style: TextStyle(
|
||||
fontSize: 12.spMin,
|
||||
color: const Color(0xFF495A7A),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 3.5.w),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE1EAFA),
|
||||
borderRadius: BorderRadius.circular(4.r),
|
||||
),
|
||||
child: Text(
|
||||
'$knowCount',
|
||||
style: TextStyle(
|
||||
fontSize: 12.spMin,
|
||||
color: const Color(0xFF495A7A),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 底部进度条:蓝渐变 / 绿渐变 =====
|
||||
Widget _buildProgressBar() {
|
||||
return SizedBox(
|
||||
height: 6.h, // 12rpx
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
final fillWidth =
|
||||
constraints.maxWidth * (progress.clamp(0, 100) / 100);
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE9EDF5),
|
||||
borderRadius: BorderRadius.circular(3.h),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: fillWidth,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3.h),
|
||||
gradient: LinearGradient(
|
||||
colors: _isFinished
|
||||
? const [Color(0xFFB4EADA), Color(0xFF19CE92)]
|
||||
: const [
|
||||
Color(0xFF8AD1FD),
|
||||
Color(0xFF3C84FF),
|
||||
Color(0xFF3D7DF5),
|
||||
],
|
||||
stops: _isFinished ? null : const [0, 0.64, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 右上角标:162x66rpx 背景图 + 状态图标 + 百分比 =====
|
||||
Widget _buildSubscript() {
|
||||
final bgImg = _isFinished ? _subscriptBgGreen : _subscriptBgBlue;
|
||||
return SizedBox(
|
||||
width: 81.w,
|
||||
height: 33.h,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(bgImg),
|
||||
fit: BoxFit.fill,
|
||||
errorWidget: (_, _, _) => DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _isFinished
|
||||
? const Color(0xFF19CE92)
|
||||
: const Color(0xFF3D7DF5),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(8.r),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 5.h),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (status != ComputationSubjectStatus.notStarted)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 4.w),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImgUrl.build(
|
||||
_isFinished ? _iconCheck : _iconFlash,
|
||||
),
|
||||
width: 18.w,
|
||||
height: 18.w,
|
||||
errorWidget: (_, _, _) =>
|
||||
SizedBox(width: 18.w, height: 18.w),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$progress%',
|
||||
style: TextStyle(
|
||||
fontSize: 16.spMin, // 32rpx
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user