import 'dart:io' as io; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../constants/app_constants.dart'; import '../../../utils/http/http_client.dart'; import 'tougao.dart'; /// 时间: 2026-03-30 /// 功能: 诗词投稿页面 /// 介绍: 用户提交诗词收录申请,支持相似度检测和投稿记录 /// 最新变化: 新增投稿记录功能 class ManuscriptPage extends StatefulWidget { const ManuscriptPage({super.key}); @override State createState() => _ManuscriptPageState(); } class _ManuscriptPageState extends State { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _urlController = TextEditingController(); final _keywordsController = TextEditingController(); final _introduceController = TextEditingController(); List> _categories = []; String? _selectedCategory; bool _isLoadingCategories = true; bool _isCheckingName = false; bool _isSubmitting = false; bool _nameExists = false; int _maxSimilarity = 0; int _similarCount = 0; bool _nameChecked = false; @override void initState() { super.initState(); _loadCategories(); } @override void dispose() { _nameController.dispose(); _urlController.dispose(); _keywordsController.dispose(); _introduceController.dispose(); super.dispose(); } String _getPlatform() { try { final String osName = io.Platform.operatingSystem; if (osName == 'ohos' || osName == 'harmonyos' || osName == 'openharmony') { return 'HarmonyOS Flutter'; } else if (io.Platform.isAndroid) { return 'Android Flutter'; } else if (io.Platform.isIOS) { return 'iOS Flutter'; } else if (io.Platform.isWindows) { return 'Windows Flutter'; } else if (io.Platform.isMacOS) { return 'macOS Flutter'; } else if (io.Platform.isLinux) { return 'Linux Flutter'; } else { return 'Flutter'; } } catch (e) { return 'Flutter'; } } Future _loadCategories() async { try { final response = await HttpClient.get( 'app/apply.php', queryParameters: {'api': 'categories'}, ); if (response.isSuccess) { final data = response.jsonData; if (data['ok'] == true && data['categories'] != null) { setState(() { _categories = List>.from(data['categories']); _isLoadingCategories = false; }); } else { setState(() => _isLoadingCategories = false); _showSnackBar('加载分类失败', isError: true); } } else { setState(() => _isLoadingCategories = false); _showSnackBar('加载分类失败', isError: true); } } catch (e) { setState(() => _isLoadingCategories = false); _showSnackBar('网络请求失败: $e', isError: true); } } Future _checkName() async { final name = _nameController.text.trim(); if (name.isEmpty) { setState(() { _nameChecked = false; _nameExists = false; }); return; } setState(() => _isCheckingName = true); try { final response = await HttpClient.postForm( 'app/apply.php?api=check-name', data: {'name': name, 'threshold': '80'}, ); if (response.isSuccess) { final data = response.jsonData; if (data['ok'] == true) { setState(() { _nameChecked = true; _nameExists = data['exists'] ?? false; _maxSimilarity = (data['max_similarity'] ?? 0).toInt(); _similarCount = (data['similar_count'] ?? 0).toInt(); }); } } } catch (e) { _showSnackBar('检测失败,请重试', isError: true); } finally { setState(() => _isCheckingName = false); } } Future _saveManuscriptRecord() async { try { final prefs = await SharedPreferences.getInstance(); final record = ManuscriptRecord( name: _nameController.text.trim(), catename: _selectedCategory ?? '', url: _urlController.text.trim(), keywords: _keywordsController.text.trim(), introduce: _introduceController.text.trim(), platform: _getPlatform(), submitTime: DateTime.now(), ); final recordsJson = prefs.getStringList('manuscript_records') ?? []; recordsJson.insert(0, jsonEncode(record.toJson())); await prefs.setStringList( 'manuscript_records', recordsJson.take(50).toList(), ); } catch (e) {} } Future _submitForm() async { if (!_formKey.currentState!.validate()) return; if (!_nameChecked) { await _checkName(); if (!_nameChecked) return; } if (_nameExists) { _showSnackBar('该诗词已存在,请更换', isError: true); return; } final confirm = await _showConfirmDialog(); if (!confirm) return; setState(() => _isSubmitting = true); try { final response = await HttpClient.postForm( 'app/apply.php?api=submit', data: { 'name': _nameController.text.trim(), 'catename': _selectedCategory, 'url': _urlController.text.trim(), 'keywords': _keywordsController.text.trim(), 'introduce': _introduceController.text.trim(), 'img': _getPlatform(), 'threshold': '80', }, ); if (response.isSuccess) { final data = response.jsonData; if (data['ok'] == true) { await _saveManuscriptRecord(); _showResultDialog(true, data['message'] ?? '✅ 提交成功!等待审核'); _resetForm(); } else { _showResultDialog(false, data['error'] ?? '提交失败'); } } else { _showResultDialog(false, response.message); } } catch (e) { _showResultDialog(false, '网络请求失败: $e'); } finally { setState(() => _isSubmitting = false); } } void _resetForm() { _formKey.currentState?.reset(); _nameController.clear(); _urlController.clear(); _keywordsController.clear(); _introduceController.clear(); setState(() { _selectedCategory = null; _nameChecked = false; _nameExists = false; _maxSimilarity = 0; _similarCount = 0; }); } Future _showConfirmDialog() async { return await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Row( children: [ Icon(Icons.help_outline, color: AppConstants.primaryColor), SizedBox(width: 8), Text('确认提交'), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildConfirmItem('投稿句子', _nameController.text), _buildConfirmItem('分类', _selectedCategory ?? ''), _buildConfirmItem('诗人和标题', _urlController.text), _buildConfirmItem('关键词', _keywordsController.text), _buildConfirmItem('平台', _getPlatform()), _buildConfirmItem('介绍', _introduceController.text, maxLines: 2), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('确认提交'), ), ], ), ) ?? false; } Widget _buildConfirmItem(String label, String value, {int maxLines = 1}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 80, child: Text( '$label:', style: TextStyle(color: Colors.grey[600], fontSize: 14), ), ), Expanded( child: Text( value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), maxLines: maxLines, overflow: TextOverflow.ellipsis, ), ), ], ), ); } void _showResultDialog(bool success, String message) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon( success ? Icons.check_circle : Icons.error, color: success ? AppConstants.successColor : AppConstants.errorColor, ), const SizedBox(width: 8), Text(success ? '提交成功' : '提交失败'), ], ), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定'), ), ], ), ); } void _showSnackBar(String message, {bool isError = false}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon( isError ? Icons.error_outline : Icons.check_circle_outline, color: Colors.white, ), const SizedBox(width: 8), Expanded(child: Text(message)), ], ), backgroundColor: isError ? AppConstants.errorColor : AppConstants.successColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), margin: const EdgeInsets.all(16), ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( title: const Text( '📝 诗词投稿', style: TextStyle( color: AppConstants.primaryColor, fontWeight: FontWeight.bold, ), ), backgroundColor: Colors.white, elevation: 0, centerTitle: true, leading: IconButton( icon: const Icon(Icons.arrow_back, color: AppConstants.primaryColor), onPressed: () => Navigator.of(context).pop(), ), actions: [ IconButton( icon: const Icon(Icons.history, color: AppConstants.primaryColor), tooltip: '投稿记录', onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const TougaoPage()), ); }, ), ], ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ _buildHeaderCard(), const SizedBox(height: 16), _buildFormCard(), const SizedBox(height: 24), _buildSubmitButton(), const SizedBox(height: 16), _buildTipsCard(), ], ), ), ); } Widget _buildHeaderCard() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor.withAlpha(30), AppConstants.primaryColor.withAlpha(10), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), ), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.edit_note, color: AppConstants.primaryColor, size: 32, ), ), const SizedBox(width: 16), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '收录经典诗词', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 4), Text( '支持原创和古诗,传承中华文化', style: TextStyle(fontSize: 14, color: Colors.grey), ), ], ), ), ], ), ); } Widget _buildFormCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ _buildSectionHeader('📝 投稿信息'), const Divider(height: 1), Padding( padding: const EdgeInsets.all(16), child: Column( children: [ _buildNameField(), const SizedBox(height: 16), _buildCategoryField(), const SizedBox(height: 16), _buildUrlField(), const SizedBox(height: 16), _buildKeywordsField(), const SizedBox(height: 16), _buildIntroduceField(), ], ), ), ], ), ); } Widget _buildSectionHeader(String title) { return Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Text( title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ); } Widget _buildNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( '参考语句', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), const Text(' *', style: TextStyle(color: AppConstants.errorColor)), const Spacer(), if (_isCheckingName) const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ), ], ), const SizedBox(height: 8), TextFormField( controller: _nameController, decoration: InputDecoration( hintText: '如:纤云弄巧,飞星传恨', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), suffixIcon: _nameChecked ? Icon( _nameExists ? Icons.error : Icons.check_circle, color: _nameExists ? AppConstants.errorColor : AppConstants.successColor, ) : null, ), validator: (value) { if (value == null || value.trim().isEmpty) { return '请输入参考语句'; } return null; }, onChanged: (value) { setState(() { _nameChecked = false; _nameExists = false; }); }, onFieldSubmitted: (_) => _checkName(), ), if (_nameChecked) _buildSimilarityInfo(), const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: TextButton.icon( onPressed: _isCheckingName ? null : _checkName, icon: const Icon(Icons.search, size: 18), label: const Text('检测是否存在'), ), ), ], ); } Widget _buildSimilarityInfo() { return Container( margin: const EdgeInsets.only(top: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _nameExists ? AppConstants.errorColor.withAlpha(20) : AppConstants.successColor.withAlpha(20), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( _nameExists ? Icons.warning : Icons.check_circle, color: _nameExists ? AppConstants.errorColor : AppConstants.successColor, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( _nameExists ? '⚠️ 发现相似内容,相似 $_similarCount 条,最高相似度 $_maxSimilarity%' : '✅ 未发现相似内容', style: TextStyle( color: _nameExists ? AppConstants.errorColor : AppConstants.successColor, fontSize: 13, ), ), ), ], ), ); } Widget _buildCategoryField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Text( '分类', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), Text(' *', style: TextStyle(color: AppConstants.errorColor)), ], ), const SizedBox(height: 8), DropdownButtonFormField( value: _selectedCategory, decoration: InputDecoration( hintText: '请选择分类', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), items: _categories.map((cat) { return DropdownMenuItem( value: cat['catename'] as String, child: Text(cat['catename'] as String), ); }).toList(), onChanged: (value) { setState(() => _selectedCategory = value); }, validator: (value) { if (value == null || value.isEmpty) { return '请选择分类'; } return null; }, ), ], ); } Widget _buildUrlField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Text( '诗人和标题', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), Text(' *', style: TextStyle(color: AppConstants.errorColor)), ], ), const SizedBox(height: 8), TextFormField( controller: _urlController, decoration: InputDecoration( hintText: '如:李白 静夜思', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), validator: (value) { if (value == null || value.trim().isEmpty) { return '请输入诗人和标题'; } return null; }, ), ], ); } Widget _buildKeywordsField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Text( '关键词', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), Text(' *', style: TextStyle(color: AppConstants.errorColor)), ], ), const SizedBox(height: 8), TextFormField( controller: _keywordsController, decoration: InputDecoration( hintText: '用逗号分隔,如:思乡,月亮,唐诗', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), validator: (value) { if (value == null || value.trim().isEmpty) { return '请输入关键词'; } return null; }, ), ], ); } Widget _buildIntroduceField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Text( '诗词介绍', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), Text(' *', style: TextStyle(color: AppConstants.errorColor)), ], ), const SizedBox(height: 8), TextFormField( controller: _introduceController, maxLines: 5, decoration: InputDecoration( hintText: '会说就多说几句...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.all(16), ), validator: (value) { if (value == null || value.trim().isEmpty) { return '请输入诗词介绍'; } return null; }, ), ], ); } Widget _buildSubmitButton() { return Container( height: 56, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor, AppConstants.primaryColor.withAlpha(200), ], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppConstants.primaryColor.withAlpha(50), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: _isSubmitting ? null : _submitForm, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), child: _isSubmitting ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.send, color: Colors.white), SizedBox(width: 8), Text( '提交收录', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ), ); } Widget _buildTipsCard() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue[50], borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lightbulb_outline, color: Colors.blue[700], size: 20), const SizedBox(width: 8), Text( '投稿提示', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.blue[700], ), ), ], ), const SizedBox(height: 8), Text( '• 支持原创诗词和经典古诗\n• 相似度超过80%将无法提交\n• 提交后等待审核通过\n• 平台信息自动识别:${_getPlatform()}', style: TextStyle( fontSize: 13, color: Colors.blue[700], height: 1.5, ), ), ], ), ); } }