feat: UI 清新风全面改造 — 朝露主题 + shadcn_ui + 全局字号放大

- 全新"朝露"主题:深青绿主色(#2D6A4F),暖白底,完整设计Token
- 引入 shadcn_ui 组件库,MaterialApp 注入 ShadTheme
- 新增 4 个公共组件:AppCard、AppMenuItem、AppEmptyState、AppTabChip
- 全面消除硬编码颜色,统一使用 AppTheme Token
- 全局字号 +3~4pt,图标等比放大 +3px
- home/medication/profile/settings/login 等核心页面重写
- 路由和功能逻辑零改动
This commit is contained in:
MingNian
2026-06-08 18:06:18 +08:00
parent 16d1d3d305
commit 58af5f6d5b
28 changed files with 1616 additions and 812 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart';
import '../../providers/data_providers.dart';
import '../../providers/consultation_provider.dart';
@@ -42,7 +44,7 @@ class DoctorListPage extends ConsumerWidget {
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 12, offset: const Offset(0, 4))
BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 12, offset: const Offset(0, 4))
],
),
child: Padding(
@@ -52,26 +54,26 @@ class DoctorListPage extends ConsumerWidget {
children: [
CircleAvatar(
radius: 28,
backgroundColor: const Color(0xFFF0F2FF),
backgroundColor: AppTheme.primaryLight,
child: Text(
(d['name'] as String?)?.isNotEmpty == true ? d['name']![0] : '?',
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7)),
style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w600, color: AppTheme.primaryLight),
),
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
color: AppTheme.primaryLight,
borderRadius: BorderRadius.circular(6),
),
child: Text(d['title'] ?? '', style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7), fontWeight: FontWeight.w500)),
child: Text(d['title'] ?? '', style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight, fontWeight: FontWeight.w500)),
),
const SizedBox(height: 6),
Text(d['department'] ?? '', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF333333))),
Text(d['department'] ?? '', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF333333))),
const SizedBox(height: 8),
Text(d['introduction'] ?? '',
style: const TextStyle(fontSize: 12, color: Color(0xFF888888), height: 1.4),
style: const TextStyle(fontSize: 15, color: Color(0xFF888888), height: 1.4),
maxLines: 4,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center),
@@ -82,12 +84,12 @@ class DoctorListPage extends ConsumerWidget {
child: ElevatedButton(
onPressed: () => pushRoute(ref, 'consultation', params: {'id': d['id']?.toString() ?? ''}),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B9CF7),
backgroundColor: AppTheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
padding: EdgeInsets.zero,
),
child: const Text('立即咨询', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
child: const Text('立即咨询', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
),
),
],
@@ -164,14 +166,14 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
final canSend = state.status == 'AiTalking' && !state.isSending;
return Scaffold(
backgroundColor: const Color(0xFFF8F9FC),
backgroundColor: AppTheme.bg,
appBar: AppBar(
backgroundColor: Colors.white,
title: Column(children: [
Text(state.doctorName.isNotEmpty ? '${state.doctorName} · ${state.doctorDepartment}' : '问诊对话',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
if (state.doctorName.isNotEmpty)
Text(_statusText(state.status), style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7))),
Text(_statusText(state.status), style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight)),
]),
centerTitle: true,
actions: [
@@ -179,16 +181,16 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
margin: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
color: AppTheme.primaryLight,
borderRadius: BorderRadius.circular(12),
),
child: Text('剩余${state.quotaRemaining}/${state.quotaTotal}',
style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7), fontWeight: FontWeight.w500)),
style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight, fontWeight: FontWeight.w500)),
),
],
),
body: state.isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7)))
? const Center(child: CircularProgressIndicator(color: AppTheme.primaryLight))
: Column(children: [
Expanded(child: _buildMessageList(state)),
_buildInputBar(canSend),
@@ -220,7 +222,7 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.82),
decoration: BoxDecoration(
color: isUser ? const Color(0xFF8B9CF7) : const Color(0xFFFEFEFF),
color: isUser ? AppTheme.primary : const Color(0xFFFEFEFF),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isUser ? 20 : 4),
topRight: Radius.circular(isUser ? 4 : 20),
@@ -230,26 +232,26 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
border: isUser ? null : Border.all(color: const Color(0xFFD8DCFD), width: 1.5),
boxShadow: isUser
? []
: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))],
: [BoxShadow(color: AppTheme.primary.withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))],
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
if (!isUser && msg.senderName != null) ...[
Row(children: [
const CircleAvatar(radius: 10, backgroundColor: Color(0xFFF0F2FF),
child: Icon(Icons.smart_toy, size: 12, color: Color(0xFF8B9CF7))),
child: Icon(Icons.smart_toy, size: 15, color: AppTheme.primaryLight)),
const SizedBox(width: 6),
Text(msg.senderName!, style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E))),
Text(msg.senderName!, style: const TextStyle(fontSize: 15, color: Color(0xFF9E9E9E))),
]),
const SizedBox(height: 8),
],
if (isUser)
Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4))
Text(msg.content, style: const TextStyle(fontSize: 19, color: Colors.white, height: 1.4))
else
MarkdownBody(
data: msg.content,
selectable: true,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5),
p: const TextStyle(fontSize: 19, color: Color(0xFF1A1A1A), height: 1.5),
),
),
if (isAi) ...[
@@ -261,7 +263,7 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
borderRadius: BorderRadius.circular(6),
),
child: Text('🏷️ 以上为AI分析具体请咨询${state.doctorName}',
style: const TextStyle(fontSize: 10, color: Color(0xFFF9A825))),
style: const TextStyle(fontSize: 13, color: Color(0xFFF9A825))),
),
],
]),
@@ -281,10 +283,10 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
child: TextField(
controller: _textCtrl,
enabled: canSend,
style: const TextStyle(fontSize: 15),
style: const TextStyle(fontSize: 18),
decoration: InputDecoration(
hintText: canSend ? '描述您的症状...' : '对话已结束',
hintStyle: const TextStyle(fontSize: 15, color: Color(0xFFBBBBBB)),
hintStyle: const TextStyle(fontSize: 18, color: Color(0xFFBBBBBB)),
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
border: InputBorder.none,
),
@@ -292,8 +294,8 @@ class _DoctorChatPageState extends ConsumerState<DoctorChatPage> {
),
),
IconButton(
icon: Icon(Icons.send, size: 24,
color: canSend ? const Color(0xFF8B9CF7) : const Color(0xFFCCCCCC)),
icon: Icon(Icons.send, size: 28,
color: canSend ? AppTheme.primary : const Color(0xFFCCCCCC)),
onPressed: canSend ? _send : null,
),
]),