Flutter药品信息查询应用开发教程

项目简介

这是一款功能完整的药品信息查询应用,提供药品基本信息、用法用量、不良反应、禁忌症、注意事项等详细信息。应用采用Material Design 3设计风格,支持分类浏览、关键词搜索、收藏管理等功能,帮助用户安全合理用药。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心特性

  • 药品分类:感冒药、消炎药、止痛药、胃药、心血管药、降压药六大分类
  • 详细信息:药品名称、通用名、生产企业、规格、价格等基本信息
  • 用药指导:适应症、用法用量详细说明
  • 安全提示:不良反应、禁忌症、注意事项完整展示
  • 搜索功能:支持药品名称、通用名、适应症关键词搜索
  • 收藏管理:收藏常用药品,快速查看
  • 处方标识:清晰标识处方药和非处方药
  • 渐变设计:精美的渐变UI设计

技术栈

  • Flutter 3.x
  • Material Design 3
  • 状态管理(setState)
  • 搜索过滤算法
  • 渐变UI设计

项目架构

MedicineHomePage

MedicineListPage

SearchPage

FavoritesPage

MedicineDetailPage

Medicine Model

数据模型设计

Medicine(药品模型)

class Medicine {
  final int id;                    // 药品ID
  final String name;               // 药品名称
  final String genericName;        // 通用名称
  final String category;           // 分类
  final String manufacturer;       // 生产企业
  final String dosageForm;         // 剂型
  final String specification;      // 规格
  final String indication;         // 适应症
  final String usage;              // 用法用量
  final String sideEffects;        // 不良反应
  final String contraindications;  // 禁忌
  final String precautions;        // 注意事项
  final double price;              // 价格
  final bool isPrescription;       // 是否处方药
  final String storage;            // 贮藏条件
}

设计要点

  • ID用于唯一标识
  • 药品名称和通用名分离
  • 完整的用药信息
  • 安全信息详细记录
  • 处方药标识
  • 价格和规格信息

药品分类

分类 示例药品 主要用途
感冒药 感冒灵颗粒、复方氨酚烷胺片 缓解感冒症状
消炎药 阿莫西林、头孢克肟 抗菌消炎
止痛药 布洛芬、对乙酰氨基酚 缓解疼痛
胃药 奥美拉唑、雷尼替丁 治疗胃病
心血管药 阿司匹林、辛伐他汀 心血管疾病
降压药 硝苯地平、缬沙坦 降低血压

剂型分类

final dosageForms = ['片剂', '胶囊', '颗粒', '口服液', '注射液'];

剂型说明

  • 片剂:固体制剂,便于携带和服用
  • 胶囊:粉末或液体装入胶囊壳
  • 颗粒:固体颗粒,需用水冲服
  • 口服液:液体制剂,吸收快
  • 注射液:无菌液体,用于注射

核心功能实现

1. 药品数据生成

使用预定义数据生成药品列表。

static List<Medicine> _generateMedicines() {
  final random = Random(42);
  final dosageForms = ['片剂', '胶囊', '颗粒', '口服液', '注射液'];
  
  final medicineData = [
    {
      'name': '阿莫西林胶囊',
      'generic': '阿莫西林',
      'category': '消炎药',
      'indication': '用于敏感菌所致的各种感染'
    },
    // 更多药品数据...
  ];

  final medicines = <Medicine>[];
  for (int i = 0; i < medicineData.length; i++) {
    final data = medicineData[i];
    medicines.add(Medicine(
      id: i + 1,
      name: data['name']!,
      genericName: data['generic']!,
      category: data['category']!,
      manufacturer: '${manufacturers[random.nextInt(4)]}有限公司',
      dosageForm: dosageForms[random.nextInt(dosageForms.length)],
      specification: '${[10, 20, 50, 100][random.nextInt(4)]}mg×${[12, 24, 30][random.nextInt(3)]}片',
      indication: data['indication']!,
      usage: '口服。成人一次1-2片,一日3次,饭后服用。',
      sideEffects: '可能出现恶心、呕吐、腹泻、皮疹等不良反应。',
      contraindications: '对本品过敏者禁用。孕妇及哺乳期妇女慎用。',
      precautions: '用药期间避免饮酒。肝肾功能不全者慎用。',
      price: (random.nextInt(50) + 10).toDouble(),
      isPrescription: random.nextBool(),
      storage: '密封,置阴凉干燥处保存。',
    ));
  }

  return medicines;
}

数据生成要点

  • 使用固定种子保证数据一致性
  • 16种常见药品
  • 随机分配生产企业和规格
  • 随机设置处方药标识

2. 分类筛选和搜索

使用计算属性实现多条件过滤。

List<Medicine> get _filteredMedicines {
  var filtered = _medicines;
  
  // 分类筛选
  if (_selectedCategory != '全部') {
    filtered = filtered.where((m) => m.category == _selectedCategory).toList();
  }
  
  // 关键词搜索
  if (_searchQuery.isNotEmpty) {
    filtered = filtered.where((m) {
      return m.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
             m.genericName.toLowerCase().contains(_searchQuery.toLowerCase()) ||
             m.indication.toLowerCase().contains(_searchQuery.toLowerCase());
    }).toList();
  }
  
  return filtered;
}

搜索范围

  • 药品名称
  • 通用名称
  • 适应症描述

3. 药品卡片设计

Widget _buildMedicineCard(Medicine medicine) {
  return Card(
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => MedicineDetailPage(medicine: medicine),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // 头部:图标、名称、处方标识
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: Colors.teal.shade50,
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(Icons.medication, color: Colors.teal.shade700),
                ),
                Expanded(
                  child: Column(
                    children: [
                      Text(medicine.name),
                      Text(medicine.genericName),
                    ],
                  ),
                ),
                if (medicine.isPrescription)
                  Container(
                    decoration: BoxDecoration(
                      color: Colors.red.shade50,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text('处方药'),
                  ),
              ],
            ),
            // 分类和生产企业
            Row(
              children: [
                _buildInfoChip(Icons.category, medicine.category, Colors.blue),
                _buildInfoChip(Icons.business, medicine.manufacturer, Colors.orange),
              ],
            ),
            // 适应症
            Text(medicine.indication, maxLines: 2),
            // 价格和规格
            Row(
              children: [
                Text(${medicine.price.toStringAsFixed(2)}'),
                Spacer(),
                Text(medicine.specification),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

卡片特点

  • 药品图标醒目
  • 处方药红色标识
  • 分类和企业信息清晰
  • 价格和规格展示

4. 信息标签组件

Widget _buildInfoChip(IconData icon, String label, Color color) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(icon, size: 14, color: color),
        SizedBox(width: 4),
        Text(
          label,
          style: TextStyle(fontSize: 11, color: color),
        ),
      ],
    ),
  );
}

标签用途

  • 分类标识(蓝色)
  • 生产企业(橙色)
  • 其他信息标识

5. 搜索页面

实时搜索功能。

Widget _buildSearchPage() {
  return Column(
    children: [
      // 搜索头部
      Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.teal.shade600, Colors.teal.shade400],
          ),
        ),
        child: Column(
          children: [
            Text('搜索药品'),
            TextField(
              controller: _searchController,
              decoration: InputDecoration(
                hintText: '输入药品名称、通用名或适应症',
                prefixIcon: Icon(Icons.search),
                suffixIcon: _searchQuery.isNotEmpty
                    ? IconButton(
                        icon: Icon(Icons.clear),
                        onPressed: () {
                          setState(() {
                            _searchController.clear();
                            _searchQuery = '';
                          });
                        },
                      )
                    : null,
                filled: true,
                fillColor: Colors.white,
              ),
              onChanged: (value) {
                setState(() {
                  _searchQuery = value;
                });
              },
            ),
          ],
        ),
      ),
      // 搜索结果
      Expanded(
        child: _searchQuery.isEmpty
            ? _buildEmptySearchState()
            : _filteredMedicines.isEmpty
                ? _buildNoResultState()
                : ListView.builder(
                    itemCount: _filteredMedicines.length,
                    itemBuilder: (context, index) {
                      return _buildMedicineCard(_filteredMedicines[index]);
                    },
                  ),
      ),
    ],
  );
}

搜索特点

  • 实时搜索,输入即显示结果
  • 清除按钮快速清空
  • 空状态和无结果状态提示

6. 药品详情页面

展示完整的药品信息。

class MedicineDetailPage extends StatelessWidget {
  final Medicine medicine;
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('药品详情'),
        actions: [
          IconButton(
            icon: Icon(Icons.favorite_border),
            onPressed: () {
              // 收藏功能
            },
          ),
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () {
              // 分享功能
            },
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            _buildHeader(context),
            _buildBasicInfo(),
            _buildUsageInfo(),
            _buildSafetyInfo(),
            _buildStorageInfo(),
          ],
        ),
      ),
    );
  }
}

详情页头部

Widget _buildHeader(BuildContext context) {
  return Container(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.teal.shade600, Colors.teal.shade400],
      ),
    ),
    child: Column(
      children: [
        Row(
          children: [
            // 药品图标
            Container(
              decoration: BoxDecoration(
                color: Colors.white.withValues(alpha: 0.2),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(Icons.medication, color: Colors.white, size: 40),
            ),
            // 药品名称
            Column(
              children: [
                Text(medicine.name, style: TextStyle(color: Colors.white)),
                Text(medicine.genericName, style: TextStyle(color: Colors.white70)),
              ],
            ),
          ],
        ),
        // 标签和价格
        Row(
          children: [
            if (medicine.isPrescription)
              Container(
                child: Text('处方药', style: TextStyle(color: Colors.white)),
              ),
            Container(
              child: Text(medicine.category, style: TextStyle(color: Colors.white)),
            ),
            Spacer(),
            Text(${medicine.price.toStringAsFixed(2)}', 
                 style: TextStyle(color: Colors.white, fontSize: 24)),
          ],
        ),
      ],
    ),
  );
}

7. 基本信息卡片

Widget _buildBasicInfo() {
  return Card(
    child: Column(
      children: [
        Row(
          children: [
            Icon(Icons.info_outline, color: Colors.teal),
            Text('基本信息'),
          ],
        ),
        _buildInfoRow('药品名称', medicine.name),
        _buildInfoRow('通用名称', medicine.genericName),
        _buildInfoRow('生产企业', medicine.manufacturer),
        _buildInfoRow('剂型', medicine.dosageForm),
        _buildInfoRow('规格', medicine.specification),
        _buildInfoRow('类型', medicine.isPrescription ? '处方药' : '非处方药'),
      ],
    ),
  );
}

Widget _buildInfoRow(String label, String value) {
  return Row(
    children: [
      SizedBox(
        width: 80,
        child: Text(label, style: TextStyle(color: Colors.grey.shade600)),
      ),
      Expanded(
        child: Text(value, style: TextStyle(fontWeight: FontWeight.w500)),
      ),
    ],
  );
}

8. 用药信息卡片

Widget _buildUsageInfo() {
  return Card(
    child: Column(
      children: [
        Row(
          children: [
            Icon(Icons.medical_services, color: Colors.blue),
            Text('用药信息'),
          ],
        ),
        _buildSection('适应症', medicine.indication, 
                     Icons.check_circle_outline, Colors.green),
        _buildSection('用法用量', medicine.usage, 
                     Icons.schedule, Colors.blue),
      ],
    ),
  );
}

9. 安全信息卡片

Widget _buildSafetyInfo() {
  return Card(
    child: Column(
      children: [
        Row(
          children: [
            Icon(Icons.warning_amber, color: Colors.orange),
            Text('安全信息'),
          ],
        ),
        _buildSection('不良反应', medicine.sideEffects, 
                     Icons.error_outline, Colors.orange),
        _buildSection('禁忌', medicine.contraindications, 
                     Icons.block, Colors.red),
        _buildSection('注意事项', medicine.precautions, 
                     Icons.info, Colors.amber),
      ],
    ),
  );
}

Widget _buildSection(String title, String content, IconData icon, Color color) {
  return Container(
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.05),
      borderRadius: BorderRadius.circular(8),
      border: Border.all(color: color.withValues(alpha: 0.2)),
    ),
    child: Column(
      children: [
        Row(
          children: [
            Icon(icon, size: 18, color: color),
            Text(title, style: TextStyle(color: color, fontWeight: FontWeight.bold)),
          ],
        ),
        Text(content, style: TextStyle(color: Colors.grey.shade700)),
      ],
    ),
  );
}

安全信息颜色

  • 不良反应:橙色(警告)
  • 禁忌:红色(严重警告)
  • 注意事项:琥珀色(提示)

UI组件设计

1. 渐变头部

Container(
  padding: const EdgeInsets.fromLTRB(16, 48, 16, 16),
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.teal.shade600, Colors.teal.shade400],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  child: Row(
    children: [
      Icon(Icons.local_pharmacy, color: Colors.white, size: 32),
      Column(
        children: [
          Text('药品信息查询', style: TextStyle(color: Colors.white)),
          Text('安全用药,健康生活', style: TextStyle(color: Colors.white70)),
        ],
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white.withValues(alpha: 0.2),
          borderRadius: BorderRadius.circular(16),
        ),
        child: Text('${_medicines.length}种药品'),
      ),
    ],
  ),
)

2. 分类标签栏

Container(
  height: 50,
  child: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemCount: categories.length,
    itemBuilder: (context, index) {
      final category = categories[index];
      final isSelected = category == _selectedCategory;
      return GestureDetector(
        onTap: () {
          setState(() {
            _selectedCategory = category;
          });
        },
        child: Container(
          decoration: BoxDecoration(
            color: isSelected ? Colors.teal : Colors.grey.shade200,
            borderRadius: BorderRadius.circular(20),
          ),
          child: Text(
            category,
            style: TextStyle(
              color: isSelected ? Colors.white : Colors.grey.shade700,
              fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
            ),
          ),
        ),
      );
    },
  ),
)

3. NavigationBar底部导航

NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() {
      _selectedIndex = index;
    });
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.medication_outlined),
      selectedIcon: Icon(Icons.medication),
      label: '药品',
    ),
    NavigationDestination(
      icon: Icon(Icons.search_outlined),
      selectedIcon: Icon(Icons.search),
      label: '搜索',
    ),
    NavigationDestination(
      icon: Icon(Icons.favorite_outline),
      selectedIcon: Icon(Icons.favorite),
      label: '收藏',
    ),
  ],
)

功能扩展建议

1. 接入真实药品数据API

使用国家药品监督管理局API或第三方药品数据库:

import 'package:http/http.dart' as http;
import 'dart:convert';

class MedicineService {
  static const String baseUrl = 'https://api.example.com/medicines';
  
  Future<List<Medicine>> searchMedicines(String keyword) async {
    final response = await http.get(
      Uri.parse('$baseUrl/search?keyword=$keyword'),
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Medicine.fromJson(json)).toList();
    }
    throw Exception('搜索药品失败');
  }
  
  Future<Medicine> getMedicineDetail(int id) async {
    final response = await http.get(
      Uri.parse('$baseUrl/$id'),
    );
    
    if (response.statusCode == 200) {
      return Medicine.fromJson(json.decode(response.body));
    }
    throw Exception('获取药品详情失败');
  }
  
  Future<List<Medicine>> getMedicinesByCategory(String category) async {
    final response = await http.get(
      Uri.parse('$baseUrl/category/$category'),
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Medicine.fromJson(json)).toList();
    }
    throw Exception('获取分类药品失败');
  }
}

2. 扫码查询

使用mobile_scanner扫描药品条形码或二维码:

import 'package:mobile_scanner/mobile_scanner.dart';

class BarcodeScannerPage extends StatefulWidget {
  
  State<BarcodeScannerPage> createState() => _BarcodeScannerPageState();
}

class _BarcodeScannerPageState extends State<BarcodeScannerPage> {
  MobileScannerController cameraController = MobileScannerController();
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('扫码查询')),
      body: MobileScanner(
        controller: cameraController,
        onDetect: (capture) {
          final List<Barcode> barcodes = capture.barcodes;
          for (final barcode in barcodes) {
            final String? code = barcode.rawValue;
            if (code != null) {
              _searchByBarcode(code);
              Navigator.pop(context);
            }
          }
        },
      ),
    );
  }
  
  Future<void> _searchByBarcode(String code) async {
    final medicine = await MedicineService().searchByBarcode(code);
    if (medicine != null) {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => MedicineDetailPage(medicine: medicine),
        ),
      );
    }
  }
}

3. 用药提醒

使用flutter_local_notifications设置用药提醒:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class MedicationReminderService {
  final FlutterLocalNotificationsPlugin _notifications = 
      FlutterLocalNotificationsPlugin();
  
  Future<void> init() async {
    const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings();
    const settings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );
    
    await _notifications.initialize(settings);
  }
  
  Future<void> scheduleMedicationReminder({
    required int id,
    required String medicineName,
    required TimeOfDay time,
    required List<int> weekdays,
  }) async {
    await _notifications.zonedSchedule(
      id,
      '用药提醒',
      '该服用 $medicineName 了',
      _nextInstanceOfTime(time, weekdays),
      NotificationDetails(
        android: AndroidNotificationDetails(
          'medication_reminder',
          '用药提醒',
          importance: Importance.high,
          priority: Priority.high,
        ),
      ),
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }
  
  TZDateTime _nextInstanceOfTime(TimeOfDay time, List<int> weekdays) {
    final now = TZDateTime.now(local);
    var scheduledDate = TZDateTime(
      local,
      now.year,
      now.month,
      now.day,
      time.hour,
      time.minute,
    );
    
    if (scheduledDate.isBefore(now)) {
      scheduledDate = scheduledDate.add(Duration(days: 1));
    }
    
    return scheduledDate;
  }
}

4. 用药记录

记录用药历史和效果:

class MedicationRecord {
  final int id;
  final Medicine medicine;
  final DateTime takenTime;
  final String dosage;
  final String notes;
  final int effectiveness;  // 1-5星评价
  
  MedicationRecord({
    required this.id,
    required this.medicine,
    required this.takenTime,
    required this.dosage,
    required this.notes,
    required this.effectiveness,
  });
}

class MedicationRecordPage extends StatefulWidget {
  
  State<MedicationRecordPage> createState() => _MedicationRecordPageState();
}

class _MedicationRecordPageState extends State<MedicationRecordPage> {
  List<MedicationRecord> _records = [];
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用药记录')),
      body: ListView.builder(
        itemCount: _records.length,
        itemBuilder: (context, index) {
          final record = _records[index];
          return Card(
            child: ListTile(
              leading: Icon(Icons.medication),
              title: Text(record.medicine.name),
              subtitle: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('用量: ${record.dosage}'),
                  Text('时间: ${_formatDateTime(record.takenTime)}'),
                  Row(
                    children: List.generate(5, (i) {
                      return Icon(
                        i < record.effectiveness ? Icons.star : Icons.star_border,
                        size: 16,
                        color: Colors.amber,
                      );
                    }),
                  ),
                ],
              ),
              trailing: IconButton(
                icon: Icon(Icons.edit),
                onPressed: () => _editRecord(record),
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addRecord,
        child: Icon(Icons.add),
      ),
    );
  }
}

5. 药物相互作用查询

检查多种药物同时使用的安全性:

class DrugInteractionChecker {
  Future<List<Interaction>> checkInteractions(List<Medicine> medicines) async {
    final medicineIds = medicines.map((m) => m.id).toList();
    
    final response = await http.post(
      Uri.parse('https://api.example.com/interactions/check'),
      body: json.encode({'medicine_ids': medicineIds}),
      headers: {'Content-Type': 'application/json'},
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Interaction.fromJson(json)).toList();
    }
    throw Exception('检查药物相互作用失败');
  }
}

class Interaction {
  final String medicine1;
  final String medicine2;
  final String severity;  // 轻度、中度、严重
  final String description;
  final String recommendation;
  
  Interaction({
    required this.medicine1,
    required this.medicine2,
    required this.severity,
    required this.description,
    required this.recommendation,
  });
}

class InteractionResultPage extends StatelessWidget {
  final List<Interaction> interactions;
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('药物相互作用')),
      body: interactions.isEmpty
          ? Center(child: Text('未发现药物相互作用'))
          : ListView.builder(
              itemCount: interactions.length,
              itemBuilder: (context, index) {
                final interaction = interactions[index];
                return Card(
                  child: ListTile(
                    leading: Icon(
                      Icons.warning,
                      color: _getSeverityColor(interaction.severity),
                    ),
                    title: Text('${interaction.medicine1} + ${interaction.medicine2}'),
                    subtitle: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('严重程度: ${interaction.severity}'),
                        Text(interaction.description),
                        Text('建议: ${interaction.recommendation}'),
                      ],
                    ),
                  ),
                );
              },
            ),
    );
  }
  
  Color _getSeverityColor(String severity) {
    switch (severity) {
      case '轻度':
        return Colors.yellow;
      case '中度':
        return Colors.orange;
      case '严重':
        return Colors.red;
      default:
        return Colors.grey;
    }
  }
}

6. 附近药店

显示附近的药店位置和库存:

import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class NearbyPharmacyPage extends StatefulWidget {
  final Medicine medicine;
  
  
  State<NearbyPharmacyPage> createState() => _NearbyPharmacyPageState();
}

class _NearbyPharmacyPageState extends State<NearbyPharmacyPage> {
  GoogleMapController? _mapController;
  Position? _currentPosition;
  List<Pharmacy> _pharmacies = [];
  
  
  void initState() {
    super.initState();
    _getCurrentLocation();
  }
  
  Future<void> _getCurrentLocation() async {
    final position = await Geolocator.getCurrentPosition();
    setState(() {
      _currentPosition = position;
    });
    _searchNearbyPharmacies();
  }
  
  Future<void> _searchNearbyPharmacies() async {
    final response = await http.get(
      Uri.parse(
        'https://api.example.com/pharmacies/nearby?'
        'lat=${_currentPosition!.latitude}&'
        'lng=${_currentPosition!.longitude}&'
        'medicine_id=${widget.medicine.id}'
      ),
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      setState(() {
        _pharmacies = data.map((json) => Pharmacy.fromJson(json)).toList();
      });
    }
  }
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('附近药店')),
      body: _currentPosition == null
          ? Center(child: CircularProgressIndicator())
          : Column(
              children: [
                Expanded(
                  child: GoogleMap(
                    initialCameraPosition: CameraPosition(
                      target: LatLng(
                        _currentPosition!.latitude,
                        _currentPosition!.longitude,
                      ),
                      zoom: 14,
                    ),
                    markers: _pharmacies.map((pharmacy) {
                      return Marker(
                        markerId: MarkerId(pharmacy.id.toString()),
                        position: LatLng(pharmacy.latitude, pharmacy.longitude),
                        infoWindow: InfoWindow(
                          title: pharmacy.name,
                          snippet: '库存: ${pharmacy.stock}',
                        ),
                      );
                    }).toSet(),
                    onMapCreated: (controller) {
                      _mapController = controller;
                    },
                  ),
                ),
                Container(
                  height: 200,
                  child: ListView.builder(
                    itemCount: _pharmacies.length,
                    itemBuilder: (context, index) {
                      final pharmacy = _pharmacies[index];
                      return ListTile(
                        leading: Icon(Icons.local_pharmacy),
                        title: Text(pharmacy.name),
                        subtitle: Text('${pharmacy.distance}km | 库存: ${pharmacy.stock}'),
                        trailing: IconButton(
                          icon: Icon(Icons.navigation),
                          onPressed: () => _navigateToPharmacy(pharmacy),
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
    );
  }
}

class Pharmacy {
  final int id;
  final String name;
  final double latitude;
  final double longitude;
  final double distance;
  final int stock;
  final String phone;
  
  Pharmacy({
    required this.id,
    required this.name,
    required this.latitude,
    required this.longitude,
    required this.distance,
    required this.stock,
    required this.phone,
  });
  
  factory Pharmacy.fromJson(Map<String, dynamic> json) {
    return Pharmacy(
      id: json['id'],
      name: json['name'],
      latitude: json['latitude'],
      longitude: json['longitude'],
      distance: json['distance'],
      stock: json['stock'],
      phone: json['phone'],
    );
  }
}

7. 用药咨询

在线咨询药师:

class PharmacistConsultationPage extends StatefulWidget {
  
  State<PharmacistConsultationPage> createState() => 
      _PharmacistConsultationPageState();
}

class _PharmacistConsultationPageState 
    extends State<PharmacistConsultationPage> {
  final TextEditingController _messageController = TextEditingController();
  final List<ChatMessage> _messages = [];
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('药师咨询'),
        subtitle: Text('在线药师为您解答'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              reverse: true,
              itemCount: _messages.length,
              itemBuilder: (context, index) {
                final message = _messages[index];
                return _buildMessageBubble(message);
              },
            ),
          ),
          _buildInputArea(),
        ],
      ),
    );
  }
  
  Widget _buildMessageBubble(ChatMessage message) {
    final isUser = message.isUser;
    return Align(
      alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
      child: Container(
        margin: EdgeInsets.all(8),
        padding: EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: isUser ? Colors.teal : Colors.grey.shade200,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Text(
          message.text,
          style: TextStyle(
            color: isUser ? Colors.white : Colors.black,
          ),
        ),
      ),
    );
  }
  
  Widget _buildInputArea() {
    return Container(
      padding: EdgeInsets.all(8),
      child: Row(
        children: [
          Expanded(
            child: TextField(
              controller: _messageController,
              decoration: InputDecoration(
                hintText: '输入您的问题...',
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(24),
                ),
              ),
            ),
          ),
          IconButton(
            icon: Icon(Icons.send),
            onPressed: _sendMessage,
          ),
        ],
      ),
    );
  }
  
  void _sendMessage() {
    if (_messageController.text.isNotEmpty) {
      setState(() {
        _messages.insert(0, ChatMessage(
          text: _messageController.text,
          isUser: true,
          time: DateTime.now(),
        ));
      });
      _messageController.clear();
      
      // 发送到服务器并获取回复
      _getPharmacistReply();
    }
  }
}

class ChatMessage {
  final String text;
  final bool isUser;
  final DateTime time;
  
  ChatMessage({
    required this.text,
    required this.isUser,
    required this.time,
  });
}

8. 药品对比

对比多种药品的信息:

class MedicineComparisonPage extends StatelessWidget {
  final List<Medicine> medicines;
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('药品对比')),
      body: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: DataTable(
          columns: [
            DataColumn(label: Text('项目')),
            ...medicines.map((m) => DataColumn(label: Text(m.name))),
          ],
          rows: [
            _buildComparisonRow('通用名', medicines.map((m) => m.genericName).toList()),
            _buildComparisonRow('生产企业', medicines.map((m) => m.manufacturer).toList()),
            _buildComparisonRow('规格', medicines.map((m) => m.specification).toList()),
            _buildComparisonRow('价格', medicines.map((m) => ${m.price}').toList()),
            _buildComparisonRow('剂型', medicines.map((m) => m.dosageForm).toList()),
            _buildComparisonRow('类型', medicines.map((m) => m.isPrescription ? '处方药' : '非处方药').toList()),
          ],
        ),
      ),
    );
  }
  
  DataRow _buildComparisonRow(String label, List<String> values) {
    return DataRow(
      cells: [
        DataCell(Text(label, style: TextStyle(fontWeight: FontWeight.bold))),
        ...values.map((v) => DataCell(Text(v))),
      ],
    );
  }
}

性能优化建议

1. 数据缓存

避免频繁请求API:

class CacheService {
  static final Map<String, CachedData> _cache = {};
  static const Duration cacheExpiry = Duration(hours: 24);
  
  static List<Medicine>? getCachedMedicines(String category) {
    final cached = _cache[category];
    if (cached == null) return null;
    
    if (DateTime.now().difference(cached.timestamp) > cacheExpiry) {
      _cache.remove(category);
      return null;
    }
    
    return cached.medicines;
  }
  
  static void cacheMedicines(String category, List<Medicine> medicines) {
    _cache[category] = CachedData(
      medicines: medicines,
      timestamp: DateTime.now(),
    );
  }
}

class CachedData {
  final List<Medicine> medicines;
  final DateTime timestamp;
  
  CachedData({required this.medicines, required this.timestamp});
}

2. 搜索优化

使用防抖减少搜索频率:

import 'dart:async';

class SearchDebouncer {
  final Duration delay;
  Timer? _timer;
  
  SearchDebouncer({this.delay = const Duration(milliseconds: 500)});
  
  void run(VoidCallback action) {
    _timer?.cancel();
    _timer = Timer(delay, action);
  }
  
  void dispose() {
    _timer?.cancel();
  }
}

// 使用示例
class _SearchPageState extends State<SearchPage> {
  final _debouncer = SearchDebouncer();
  
  
  void dispose() {
    _debouncer.dispose();
    super.dispose();
  }
  
  void _onSearchChanged(String query) {
    _debouncer.run(() {
      setState(() {
        _searchQuery = query;
      });
    });
  }
}

3. 列表优化

使用ListView.builder按需构建:

ListView.builder(
  itemCount: _filteredMedicines.length,
  itemBuilder: (context, index) {
    return MedicineCard(medicine: _filteredMedicines[index]);
  },
)

4. 图片优化

使用缓存网络图片:

import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: medicine.imageUrl,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.medication),
)

测试建议

1. 单元测试

测试搜索和筛选逻辑:

void main() {
  group('药品搜索测试', () {
    final medicines = [
      Medicine(
        id: 1,
        name: '阿莫西林胶囊',
        genericName: '阿莫西林',
        category: '消炎药',
        manufacturer: '华北制药',
        dosageForm: '胶囊',
        specification: '0.25g×24粒',
        indication: '用于敏感菌所致的各种感染',
        usage: '口服',
        sideEffects: '恶心、呕吐',
        contraindications: '过敏者禁用',
        precautions: '慎用',
        price: 15.0,
        isPrescription: true,
        storage: '密封保存',
      ),
      Medicine(
        id: 2,
        name: '布洛芬缓释胶囊',
        genericName: '布洛芬',
        category: '止痛药',
        manufacturer: '扬子江药业',
        dosageForm: '胶囊',
        specification: '0.3g×20粒',
        indication: '用于缓解轻至中度疼痛',
        usage: '口服',
        sideEffects: '胃肠道反应',
        contraindications: '消化道溃疡禁用',
        precautions: '饭后服用',
        price: 20.0,
        isPrescription: false,
        storage: '密封保存',
      ),
    ];
    
    test('按名称搜索', () {
      final result = medicines.where((m) => 
        m.name.contains('阿莫西林')
      ).toList();
      expect(result.length, 1);
      expect(result[0].name, '阿莫西林胶囊');
    });
    
    test('按分类筛选', () {
      final result = medicines.where((m) => 
        m.category == '消炎药'
      ).toList();
      expect(result.length, 1);
      expect(result[0].category, '消炎药');
    });
    
    test('按处方药筛选', () {
      final result = medicines.where((m) => 
        m.isPrescription
      ).toList();
      expect(result.length, 1);
      expect(result[0].isPrescription, true);
    });
  });
}

2. Widget测试

测试UI组件:

void main() {
  testWidgets('药品卡片显示测试', (WidgetTester tester) async {
    final medicine = Medicine(
      id: 1,
      name: '阿莫西林胶囊',
      genericName: '阿莫西林',
      category: '消炎药',
      manufacturer: '华北制药',
      dosageForm: '胶囊',
      specification: '0.25g×24粒',
      indication: '用于敏感菌所致的各种感染',
      usage: '口服',
      sideEffects: '恶心、呕吐',
      contraindications: '过敏者禁用',
      precautions: '慎用',
      price: 15.0,
      isPrescription: true,
      storage: '密封保存',
    );
    
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: MedicineCard(medicine: medicine),
        ),
      ),
    );
    
    expect(find.text('阿莫西林胶囊'), findsOneWidget);
    expect(find.text('阿莫西林'), findsOneWidget);
    expect(find.text('消炎药'), findsOneWidget);
    expect(find.text('处方药'), findsOneWidget);
    expect(find.text('¥15.00'), findsOneWidget);
  });
  
  testWidgets('搜索功能测试', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    
    // 切换到搜索页面
    await tester.tap(find.text('搜索'));
    await tester.pumpAndSettle();
    
    // 输入搜索关键词
    await tester.enterText(find.byType(TextField), '阿莫西林');
    await tester.pumpAndSettle();
    
    // 验证搜索结果
    expect(find.text('阿莫西林胶囊'), findsWidgets);
  });
}

3. 集成测试

测试完整流程:

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('完整药品查询流程', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    
    // 1. 验证初始显示
    expect(find.text('药品信息查询'), findsOneWidget);
    expect(find.text('药品'), findsOneWidget);
    
    // 2. 切换分类
    await tester.tap(find.text('消炎药'));
    await tester.pumpAndSettle();
    
    // 3. 点击药品卡片
    await tester.tap(find.byType(Card).first);
    await tester.pumpAndSettle();
    expect(find.text('药品详情'), findsOneWidget);
    
    // 4. 收藏药品
    await tester.tap(find.byIcon(Icons.favorite_border));
    await tester.pumpAndSettle();
    expect(find.text('已添加到收藏'), findsOneWidget);
    
    // 5. 返回并切换到搜索页面
    await tester.pageBack();
    await tester.pumpAndSettle();
    await tester.tap(find.text('搜索'));
    await tester.pumpAndSettle();
    
    // 6. 搜索药品
    await tester.enterText(find.byType(TextField), '阿莫西林');
    await tester.pumpAndSettle();
    expect(find.text('阿莫西林胶囊'), findsWidgets);
    
    // 7. 切换到收藏页面
    await tester.tap(find.text('收藏'));
    await tester.pumpAndSettle();
    expect(find.text('我的收藏'), findsOneWidget);
  });
}

部署发布

1. Android打包

# 生成签名密钥
keytool -genkey -v -keystore ~/medicine-info-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias medicineinfo

# 配置android/key.properties
storePassword=your_password
keyPassword=your_password
keyAlias=medicineinfo
storeFile=/path/to/medicine-info-key.jks

# 构建APK
flutter build apk --release

# 构建App Bundle
flutter build appbundle --release

2. iOS打包

# 安装依赖
cd ios && pod install

# 构建IPA
flutter build ipa --release

3. 应用配置

pubspec.yaml中配置:

name: medicine_info
description: 药品信息查询应用
version: 1.0.0+1

flutter:
  uses-material-design: true

AndroidManifest.xml中配置权限:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<application
    android:label="药品信息查询"
    android:icon="@mipmap/ic_launcher">

项目总结

技术亮点

  1. 完整信息展示:15个字段全面展示药品信息
  2. 多维度搜索:支持名称、通用名、适应症搜索
  3. 分类筛选:6大药品分类快速筛选
  4. 安全提示:不良反应、禁忌、注意事项醒目展示
  5. 处方标识:清晰区分处方药和非处方药
  6. 渐变设计:精美的渐变UI提升视觉效果
  7. Material Design 3:使用最新的NavigationBar组件

学习收获

通过本项目,你将掌握:

  • 复杂数据模型设计
  • 多条件搜索和筛选
  • 分类标签栏实现
  • 详情页面布局
  • 信息卡片设计
  • 颜色语义化使用
  • 安全信息展示
  • 搜索防抖优化

应用场景

本应用适合以下场景:

  • 家庭用药:查询常用药品信息
  • 安全用药:了解药品禁忌和注意事项
  • 药品对比:对比不同药品的信息
  • 用药指导:正确使用药品
  • 学习案例:作为Flutter医疗应用的学习项目

后续优化方向

  1. 真实数据:接入国家药监局药品数据库
  2. 扫码查询:扫描药品条形码快速查询
  3. 用药提醒:设置定时用药提醒
  4. 用药记录:记录用药历史和效果
  5. 相互作用:检查药物相互作用
  6. 附近药店:显示附近药店位置和库存
  7. 在线咨询:药师在线解答用药问题
  8. 药品对比:对比多种药品信息

代码规范

项目遵循以下规范:

  • 使用const构造函数优化性能
  • 组件拆分,职责单一
  • 命名规范:驼峰命名法
  • 注释清晰,代码可读性强
  • 数据和UI分离
  • 响应式布局设计

项目结构

lib/
├── main.dart                    # 主入口文件
├── models/                      # 数据模型(可扩展)
│   ├── medicine.dart
│   ├── medication_record.dart
│   └── pharmacy.dart
├── pages/                       # 页面组件(可扩展)
│   ├── medicine_list_page.dart
│   ├── search_page.dart
│   ├── favorites_page.dart
│   ├── medicine_detail_page.dart
│   ├── barcode_scanner_page.dart
│   └── nearby_pharmacy_page.dart
├── widgets/                     # 自定义组件(可扩展)
│   ├── medicine_card.dart
│   ├── info_chip.dart
│   └── section_card.dart
└── services/                    # 业务逻辑(可扩展)
    ├── medicine_service.dart
    ├── cache_service.dart
    ├── reminder_service.dart
    └── interaction_checker.dart

相关资源

推荐API

  • 国家药品监督管理局:https://www.nmpa.gov.cn
  • 药品数据库API:https://www.meddra.org
  • 医药数据平台:https://www.yaozh.com

推荐库

  • http:网络请求
  • mobile_scanner:条形码扫描
  • flutter_local_notifications:本地通知
  • geolocator:定位功能
  • google_maps_flutter:地图显示
  • cached_network_image:图片缓存
  • sqflite:本地数据库

学习资源

  • Flutter官方文档:https://flutter.dev
  • Material Design 3:https://m3.material.io
  • Dart语言教程:https://dart.dev

药品信息扩展

可以添加更多药品信息字段:

class ExtendedMedicine extends Medicine {
  final String approvalNumber;      // 批准文号
  final DateTime approvalDate;      // 批准日期
  final String validityPeriod;      // 有效期
  final List<String> ingredients;   // 成分
  final String pharmacology;        // 药理作用
  final String overdose;            // 过量处理
  final List<String> images;        // 药品图片
  final double rating;              // 用户评分
  final int reviewCount;            // 评论数量
  
  ExtendedMedicine({
    required super.id,
    required super.name,
    required super.genericName,
    required super.category,
    required super.manufacturer,
    required super.dosageForm,
    required super.specification,
    required super.indication,
    required super.usage,
    required super.sideEffects,
    required super.contraindications,
    required super.precautions,
    required super.price,
    required super.isPrescription,
    required super.storage,
    required this.approvalNumber,
    required this.approvalDate,
    required this.validityPeriod,
    required this.ingredients,
    required this.pharmacology,
    required this.overdose,
    required this.images,
    required this.rating,
    required this.reviewCount,
  });
}

用药安全提示

应用中应包含以下安全提示:

final safetyTips = [
  '请仔细阅读药品说明书',
  '按照医生或药师指导用药',
  '不要随意增减药量',
  '注意药品有效期',
  '妥善保管药品,避免儿童接触',
  '如有不适,及时就医',
  '不要与他人共用处方药',
  '注意药物相互作用',
  '孕妇和哺乳期妇女用药需咨询医生',
  '过敏体质者用药需谨慎',
];

class SafetyTipsWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.health_and_safety, color: Colors.red),
                SizedBox(width: 8),
                Text(
                  '用药安全提示',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            SizedBox(height: 12),
            ...safetyTips.map((tip) {
              return Padding(
                padding: EdgeInsets.only(bottom: 8),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Icon(Icons.check_circle, size: 16, color: Colors.green),
                    SizedBox(width: 8),
                    Expanded(
                      child: Text(tip, style: TextStyle(fontSize: 13)),
                    ),
                  ],
                ),
              );
            }),
          ],
        ),
      ),
    );
  }
}

用户评价系统

添加用户评价功能:

class MedicineReview {
  final int id;
  final int medicineId;
  final String username;
  final double rating;
  final String comment;
  final DateTime createdAt;
  final List<String> tags;  // 如:效果好、副作用小等
  
  MedicineReview({
    required this.id,
    required this.medicineId,
    required this.username,
    required this.rating,
    required this.comment,
    required this.createdAt,
    required this.tags,
  });
}

class ReviewsPage extends StatelessWidget {
  final Medicine medicine;
  final List<MedicineReview> reviews;
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用户评价')),
      body: Column(
        children: [
          _buildRatingSummary(),
          Expanded(
            child: ListView.builder(
              itemCount: reviews.length,
              itemBuilder: (context, index) {
                return _buildReviewCard(reviews[index]);
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showReviewDialog(context),
        child: Icon(Icons.rate_review),
      ),
    );
  }
  
  Widget _buildRatingSummary() {
    final avgRating = reviews.isEmpty
        ? 0.0
        : reviews.map((r) => r.rating).reduce((a, b) => a + b) / reviews.length;
    
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Row(
          children: [
            Column(
              children: [
                Text(
                  avgRating.toStringAsFixed(1),
                  style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
                ),
                Row(
                  children: List.generate(5, (i) {
                    return Icon(
                      i < avgRating.round() ? Icons.star : Icons.star_border,
                      color: Colors.amber,
                    );
                  }),
                ),
                Text('${reviews.length}条评价'),
              ],
            ),
            SizedBox(width: 24),
            Expanded(
              child: Column(
                children: List.generate(5, (i) {
                  final star = 5 - i;
                  final count = reviews.where((r) => r.rating.round() == star).length;
                  final percentage = reviews.isEmpty ? 0.0 : count / reviews.length;
                  return Row(
                    children: [
                      Text('$star星'),
                      SizedBox(width: 8),
                      Expanded(
                        child: LinearProgressIndicator(
                          value: percentage,
                          backgroundColor: Colors.grey.shade200,
                        ),
                      ),
                      SizedBox(width: 8),
                      Text('$count'),
                    ],
                  );
                }),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

国际化支持

支持多语言:

// l10n/app_zh.arb
{
  "appTitle": "药品信息查询",
  "medicines": "药品",
  "search": "搜索",
  "favorites": "收藏",
  "medicineDetail": "药品详情",
  "basicInfo": "基本信息",
  "usageInfo": "用药信息",
  "safetyInfo": "安全信息",
  "indication": "适应症",
  "usage": "用法用量",
  "sideEffects": "不良反应",
  "contraindications": "禁忌",
  "precautions": "注意事项",
  "storage": "贮藏条件",
  "prescription": "处方药",
  "nonPrescription": "非处方药"
}

// l10n/app_en.arb
{
  "appTitle": "Medicine Information",
  "medicines": "Medicines",
  "search": "Search",
  "favorites": "Favorites",
  "medicineDetail": "Medicine Detail",
  "basicInfo": "Basic Information",
  "usageInfo": "Usage Information",
  "safetyInfo": "Safety Information",
  "indication": "Indication",
  "usage": "Usage",
  "sideEffects": "Side Effects",
  "contraindications": "Contraindications",
  "precautions": "Precautions",
  "storage": "Storage",
  "prescription": "Prescription",
  "nonPrescription": "OTC"
}

本项目提供了完整的药品信息查询应用功能,代码结构清晰,易于扩展。你可以在此基础上添加更多功能,打造一款功能丰富的医疗健康应用。药品数据可以根据需要接入真实的药品数据库API,实现真正的药品信息查询功能。

重要提示:本应用仅供学习参考,实际使用时必须接入权威的药品数据库,并遵守相关医疗法规。用药请遵医嘱,不要自行诊断和用药。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐