generated from dellevin/template
新增影视分享的几种样式
This commit is contained in:
BIN
assets/images/ticket/maoyan.png
Normal file
BIN
assets/images/ticket/maoyan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/images/ticket/qr.png
Normal file
BIN
assets/images/ticket/qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -9,10 +8,8 @@ import '../../models/data_models.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
|
||||
/// 影视分享海报页面
|
||||
class MovieSharePage extends StatefulWidget {
|
||||
final Movie movie;
|
||||
|
||||
const MovieSharePage({super.key, required this.movie});
|
||||
|
||||
@override
|
||||
@@ -22,6 +19,72 @@ class MovieSharePage extends StatefulWidget {
|
||||
class _MovieSharePageState extends State<MovieSharePage> {
|
||||
final GlobalKey _posterKey = GlobalKey();
|
||||
bool _isGenerating = false;
|
||||
int _currentStyle = 0;
|
||||
|
||||
static const _styleNames = ['海报', '经典票根', '喵眼电影票根', '收藏版票根'];
|
||||
|
||||
// 影院信息(可编辑)
|
||||
late TextEditingController _cinemaCtrl;
|
||||
late TextEditingController _hallCtrl;
|
||||
late TextEditingController _seatRowCtrl;
|
||||
late TextEditingController _seatNumCtrl;
|
||||
late TextEditingController _screeningDateCtrl;
|
||||
late TextEditingController _screeningTimeCtrl;
|
||||
late TextEditingController _priceCtrl;
|
||||
late TextEditingController _serviceFeeCtrl;
|
||||
late TextEditingController _issueTimeCtrl;
|
||||
late TextEditingController _platformCtrl;
|
||||
late TextEditingController _ticketCodeCtrl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final now = DateTime.now();
|
||||
final dateStr = '${now.year}年${now.month}月${now.day}日';
|
||||
final timeStr = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
|
||||
final issueStr = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||||
final codeStr = 'MK${now.millisecondsSinceEpoch.toString().substring(1)}';
|
||||
|
||||
_cinemaCtrl = TextEditingController(text: '万达影城');
|
||||
_hallCtrl = TextEditingController(text: '3号厅');
|
||||
_seatRowCtrl = TextEditingController(text: '8');
|
||||
_seatNumCtrl = TextEditingController(text: '12');
|
||||
_screeningDateCtrl = TextEditingController(text: dateStr);
|
||||
_screeningTimeCtrl = TextEditingController(text: timeStr);
|
||||
_priceCtrl = TextEditingController(text: '39.90');
|
||||
_serviceFeeCtrl = TextEditingController(text: '5.00');
|
||||
_issueTimeCtrl = TextEditingController(text: issueStr);
|
||||
_platformCtrl = TextEditingController(text: '喵眼电影');
|
||||
_ticketCodeCtrl = TextEditingController(text: codeStr);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cinemaCtrl.dispose();
|
||||
_hallCtrl.dispose();
|
||||
_seatRowCtrl.dispose();
|
||||
_seatNumCtrl.dispose();
|
||||
_screeningDateCtrl.dispose();
|
||||
_screeningTimeCtrl.dispose();
|
||||
_priceCtrl.dispose();
|
||||
_serviceFeeCtrl.dispose();
|
||||
_issueTimeCtrl.dispose();
|
||||
_platformCtrl.dispose();
|
||||
_ticketCodeCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _isCinemaStyle => _currentStyle >= 2;
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (_currentStyle) {
|
||||
case 0: return _buildPosterWidget();
|
||||
case 1: return _buildTicketClassic();
|
||||
case 2: return _buildTicketCinema();
|
||||
case 3: return _buildTicketCollectible();
|
||||
default: return _buildPosterWidget();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -31,53 +94,232 @@ class _MovieSharePageState extends State<MovieSharePage> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: colors.surface,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.close, color: colors.onSurface),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
'分享海报',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
),
|
||||
leading: IconButton(icon: Icon(Icons.close, color: colors.onSurface), onPressed: () => Navigator.pop(context)),
|
||||
title: Text('分享海报', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(icon: Icon(Icons.palette_outlined, color: colors.onSurface, size: 22), tooltip: '选择样式', onPressed: _showStylePicker),
|
||||
TextButton(
|
||||
onPressed: _isGenerating ? null : _generateAndShare,
|
||||
child: _isGenerating
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text(
|
||||
'分享',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
),
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: Text('分享', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: RepaintBoundary(
|
||||
key: _posterKey,
|
||||
child: _buildPosterWidget(),
|
||||
body: Column(children: [
|
||||
// 预览
|
||||
Expanded(child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: RepaintBoundary(key: _posterKey, child: _buildBody()),
|
||||
),
|
||||
)),
|
||||
// 底部编辑按钮(仅票根样式)
|
||||
if (_isCinemaStyle) _buildBottomEditBar(colors),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 样式选择 ───
|
||||
|
||||
void _showStylePicker() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: colors.surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
builder: (ctx) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 24),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text('选择样式', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(spacing: 10, runSpacing: 10, children: List.generate(_styleNames.length, (i) {
|
||||
final selected = _currentStyle == i;
|
||||
return GestureDetector(
|
||||
onTap: () { setState(() => _currentStyle = i); Navigator.pop(ctx); },
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? colors.primary : colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: selected ? colors.primary : colors.outline, width: 0.5),
|
||||
),
|
||||
child: Text(_styleNames[i], style: TextStyle(fontSize: 14, fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||
color: selected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6))),
|
||||
),
|
||||
);
|
||||
})),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 底部编辑栏 ───
|
||||
|
||||
Widget _buildBottomEditBar(ColorScheme colors) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: MediaQuery.of(context).padding.bottom + 12, top: 8),
|
||||
color: colors.surface,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 44,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _showEditSheet,
|
||||
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||
label: const Text('编辑票根信息'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: colors.primary,
|
||||
foregroundColor: colors.onPrimary,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建海报 Widget
|
||||
void _showEditSheet() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: colors.surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
builder: (ctx) => Padding(
|
||||
padding: EdgeInsets.fromLTRB(20, 16, 20, MediaQuery.of(ctx).viewInsets.bottom + 20),
|
||||
child: SingleChildScrollView(child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('编辑票根信息', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
const SizedBox(height: 16),
|
||||
_sheetField('影院名称', _cinemaCtrl),
|
||||
_sheetField('影厅', _hallCtrl),
|
||||
Row(children: [
|
||||
Expanded(child: _sheetField('排', _seatRowCtrl)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _sheetField('座', _seatNumCtrl)),
|
||||
]),
|
||||
Row(children: [
|
||||
Expanded(child: _sheetField('票价 (¥)', _priceCtrl)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _sheetField('服务费 (¥)', _serviceFeeCtrl)),
|
||||
]),
|
||||
_sheetDatePicker('出票日期', _issueTimeCtrl, ctx),
|
||||
_sheetCodeField(),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity, height: 44,
|
||||
child: ElevatedButton(
|
||||
onPressed: () { setState(() {}); Navigator.pop(ctx); },
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: const Text('完成'),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sheetField(String label, TextEditingController ctrl) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: TextField(
|
||||
controller: ctrl,
|
||||
style: TextStyle(fontSize: 14, color: colors.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5)),
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
filled: true, fillColor: colors.surfaceContainerHighest,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sheetDatePicker(String label, TextEditingController ctrl, BuildContext sheetCtx) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final now = DateTime.now();
|
||||
final picked = await showDatePicker(
|
||||
context: sheetCtx,
|
||||
initialDate: now,
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(now.year + 10),
|
||||
locale: const Locale('zh'),
|
||||
);
|
||||
if (picked != null) {
|
||||
ctrl.text = '${picked.year}-${picked.month.toString().padLeft(2, '0')}-${picked.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
},
|
||||
child: AbsorbPointer(child: TextField(
|
||||
controller: ctrl,
|
||||
style: TextStyle(fontSize: 14, color: colors.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5)),
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
filled: true, fillColor: colors.surfaceContainerHighest,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||
suffixIcon: Icon(Icons.calendar_today_outlined, size: 18, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sheetCodeField() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(children: [
|
||||
Expanded(child: TextField(
|
||||
controller: _ticketCodeCtrl,
|
||||
style: TextStyle(fontSize: 14, color: colors.onSurface, fontFamily: 'monospace'),
|
||||
decoration: InputDecoration(
|
||||
labelText: '编码',
|
||||
labelStyle: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5)),
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
filled: true, fillColor: colors.surfaceContainerHighest,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||
),
|
||||
)),
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_ticketCodeCtrl.text = 'MK${DateTime.now().millisecondsSinceEpoch.toString().substring(1)}';
|
||||
},
|
||||
child: Container(
|
||||
width: 40, height: 40,
|
||||
decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8)),
|
||||
child: Icon(Icons.casino_outlined, size: 20, color: colors.onSurface.withValues(alpha: 0.6)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 样式 0:海报
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Widget _buildPosterWidget() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final movie = widget.movie;
|
||||
@@ -85,240 +327,389 @@ class _MovieSharePageState extends State<MovieSharePage> {
|
||||
|
||||
return Container(
|
||||
width: 320,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10))]),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (hasPoster)
|
||||
ClipRRect(borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
child: FadeInLocalImage(path: movie.posterPath, width: 320, height: 200, fit: BoxFit.cover)),
|
||||
Padding(padding: const EdgeInsets.all(20), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(movie.title, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: colors.onSurface)),
|
||||
if (movie.alternateTitles.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(movie.alternateTitles.join(' / '), style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6))),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
if (movie.rating != null && movie.rating! > 0) ...[
|
||||
Row(children: [
|
||||
const Icon(Icons.star, size: 18, color: Color(0xFFFFB800)),
|
||||
const SizedBox(width: 4),
|
||||
Text(movie.rating!.toStringAsFixed(1), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFFFB800))),
|
||||
const SizedBox(width: 4),
|
||||
Text('/ 10', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (movie.directors.isNotEmpty) _infoRow('导演', movie.directors.join(' / '), colors),
|
||||
if (movie.writers.isNotEmpty) _infoRow('编剧', movie.writers.join(' / '), colors),
|
||||
if (movie.actors.isNotEmpty) _infoRow('主演', movie.actors.take(3).join(' / '), colors),
|
||||
if (movie.genres.isNotEmpty) _infoRow('类型', movie.genres.join(' / '), colors),
|
||||
if (movie.releaseDate != null) _infoRow('上映', _fmtDate(movie.releaseDate!), colors),
|
||||
if (movie.summary != null && movie.summary!.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text('简介', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||
const SizedBox(height: 8),
|
||||
Text(movie.summary!, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6), maxLines: 5, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
Divider(height: 1, color: colors.outline),
|
||||
const SizedBox(height: 12),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Icon(Icons.movie_outlined, size: 14, color: colors.onSurface.withValues(alpha: 0.5)),
|
||||
const SizedBox(width: 6),
|
||||
Text('来自 MookNote', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5))),
|
||||
]),
|
||||
])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(String label, String value, ColorScheme colors) {
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 8), child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text('$label:', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||
Expanded(child: Text(value, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.75)))),
|
||||
]));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 样式 1:经典票根
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Widget _buildTicketClassic() {
|
||||
final movie = widget.movie;
|
||||
final hasPoster = movie.posterPath != null && movie.posterPath!.isNotEmpty;
|
||||
const c = Color(0xFF2D2D2D);
|
||||
|
||||
return Container(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(color: const Color(0xFFFFFBF5), borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.08), blurRadius: 16, offset: const Offset(0, 6))]),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Padding(padding: const EdgeInsets.all(16), child: Column(children: [
|
||||
if (hasPoster) ClipRRect(borderRadius: BorderRadius.circular(4),
|
||||
child: FadeInLocalImage(path: movie.posterPath, width: 268, height: 160, fit: BoxFit.cover)),
|
||||
const SizedBox(height: 12),
|
||||
Text(movie.title, textAlign: TextAlign.center, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: c, letterSpacing: 1)),
|
||||
if (movie.alternateTitles.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(movie.alternateTitles.join(' / '), textAlign: TextAlign.center, style: TextStyle(fontSize: 11, color: c.withValues(alpha: 0.4))),
|
||||
],
|
||||
])),
|
||||
_dashedLine(c.withValues(alpha: 0.15)),
|
||||
Padding(padding: const EdgeInsets.fromLTRB(20, 14, 20, 16), child: Column(children: [
|
||||
_classicRow('DIRECTED BY', movie.directors.isNotEmpty ? movie.directors.join(', ') : '--'),
|
||||
const SizedBox(height: 10),
|
||||
_classicRow('STARRING', movie.actors.isNotEmpty ? movie.actors.take(3).join(', ') : '--'),
|
||||
const SizedBox(height: 10),
|
||||
_classicRow('GENRE', movie.genres.isNotEmpty ? movie.genres.join(' / ') : '--'),
|
||||
const SizedBox(height: 10),
|
||||
_classicRow('RELEASE', movie.releaseDate != null ? _fmtDate(movie.releaseDate!) : '--'),
|
||||
if (movie.rating != null && movie.rating! > 0) ...[
|
||||
const SizedBox(height: 10),
|
||||
_classicRow('RATING', '${movie.rating!.toStringAsFixed(1)} / 10'),
|
||||
],
|
||||
const SizedBox(height: 14),
|
||||
Row(children: [
|
||||
Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(border: Border.all(color: c.withValues(alpha: 0.2), width: 0.5)),
|
||||
child: Text(_statusEN(movie.status), style: TextStyle(fontSize: 9, fontWeight: FontWeight.w600, letterSpacing: 2, color: c.withValues(alpha: 0.5)))),
|
||||
const Spacer(),
|
||||
Icon(Icons.movie_outlined, size: 12, color: c.withValues(alpha: 0.3)),
|
||||
const SizedBox(width: 4),
|
||||
Text('MookNote', style: TextStyle(fontSize: 9, letterSpacing: 1, color: c.withValues(alpha: 0.3))),
|
||||
]),
|
||||
])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _classicRow(String label, String value) {
|
||||
return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(width: 80, child: Text(label, style: TextStyle(fontSize: 9, fontWeight: FontWeight.w600, letterSpacing: 1.5, color: const Color(0xFF2D2D2D).withValues(alpha: 0.35)))),
|
||||
Expanded(child: Text(value, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF2D2D2D), height: 1.4))),
|
||||
]);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 样式 2:电影票(参考票根.html)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Widget _buildTicketCinema() {
|
||||
final movie = widget.movie;
|
||||
const bg = Color(0xFFFFFDF7);
|
||||
const text = Color(0xFF1A1A1A);
|
||||
const muted = Color(0xFF999999);
|
||||
|
||||
return Container(
|
||||
width: 340,
|
||||
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.12), blurRadius: 24, offset: const Offset(0, 6))]),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
// Header
|
||||
Padding(padding: const EdgeInsets.fromLTRB(20, 16, 20, 14), child: Row(children: [
|
||||
Image.asset('assets/images/ticket/maoyan.png', width: 36, height: 36, errorBuilder: (_, __, ___) => Icon(Icons.local_movies, size: 36, color: text)),
|
||||
const SizedBox(width: 10),
|
||||
Text(_platformCtrl.text.isNotEmpty ? _platformCtrl.text : 'MookNote', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: text, letterSpacing: 0.5)),
|
||||
])),
|
||||
Container(height: 0.5, color: const Color(0xFFF0EDE5)),
|
||||
|
||||
// Body
|
||||
Padding(padding: const EdgeInsets.fromLTRB(20, 18, 20, 14), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
// 影院
|
||||
Text(_cinemaCtrl.text, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: text, letterSpacing: 0.5)),
|
||||
const SizedBox(height: 14),
|
||||
// 片名
|
||||
Text(movie.title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: text, letterSpacing: 0.5, height: 1.3)),
|
||||
const SizedBox(height: 4),
|
||||
Text(_subtitleText(movie), style: const TextStyle(fontSize: 12, color: Color(0xFF666666), letterSpacing: 0.5)),
|
||||
const SizedBox(height: 16),
|
||||
// 场次
|
||||
Row(children: [
|
||||
Icon(Icons.desktop_windows_outlined, size: 15, color: text.withValues(alpha: 0.6)),
|
||||
const SizedBox(width: 8),
|
||||
Text(_hallCtrl.text, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: text.withValues(alpha: 0.8))),
|
||||
]),
|
||||
const SizedBox(height: 8),
|
||||
Row(children: [
|
||||
Text('${_seatRowCtrl.text}排${_seatNumCtrl.text}座', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: text.withValues(alpha: 0.8))),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: Text('|', style: TextStyle(color: const Color(0xFFD0CCC0), fontWeight: FontWeight.w300))),
|
||||
Text(widget.movie.releaseDate != null ? '${widget.movie.releaseDate!.year}年${widget.movie.releaseDate!.month}月${widget.movie.releaseDate!.day}日' : '待定', style: TextStyle(fontSize: 14, color: text.withValues(alpha: 0.6))),
|
||||
]),
|
||||
])),
|
||||
|
||||
// 撕票线
|
||||
_tearScissors(bg),
|
||||
|
||||
// Footer
|
||||
Padding(padding: const EdgeInsets.fromLTRB(20, 10, 20, 16), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Container(width: 100, height: 100, decoration: BoxDecoration(border: Border.all(color: const Color(0xFFE8E4DA), width: 0.5)),
|
||||
child: Image.asset('assets/images/ticket/qr.png', fit: BoxFit.contain,
|
||||
errorBuilder: (_, __, ___) => const SizedBox.shrink())),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
_ticketDetail('票价', _priceCtrl.text, isPrice: true),
|
||||
_ticketDetail('服务费', _serviceFeeCtrl.text),
|
||||
_ticketDetail('出票日期', _issueTimeCtrl.text),
|
||||
_ticketDetail(_platformCtrl.text.isNotEmpty ? _platformCtrl.text : '平台', '已出票'),
|
||||
const SizedBox(height: 4),
|
||||
Text(_ticketCodeCtrl.text, style: TextStyle(fontFamily: 'monospace', fontSize: 9, letterSpacing: 0.8, color: muted)),
|
||||
])),
|
||||
])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _ticketDetail(String label, String value, {bool isPrice = false}) {
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 4), child: Row(children: [
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: Color(0xFF999999))),
|
||||
const Spacer(),
|
||||
Text(value, style: TextStyle(
|
||||
fontSize: isPrice ? 16 : 12,
|
||||
fontWeight: isPrice ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isPrice ? const Color(0xFFE5342F) : const Color(0xFF1A1A1A),
|
||||
)),
|
||||
]));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 样式 3:收藏版票根(参考票根收藏版.html)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Widget _buildTicketCollectible() {
|
||||
final movie = widget.movie;
|
||||
const red = Color(0xFFC0392B);
|
||||
const text = Color(0xFF1A1A1A);
|
||||
const light = Color(0xFF8C8C8C);
|
||||
const line = Color(0xFFE8E2D8);
|
||||
|
||||
return Container(
|
||||
width: 360,
|
||||
decoration: BoxDecoration(color: const Color(0xFFFFFFFB),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 12, offset: const Offset(0, 2)),
|
||||
BoxShadow(color: Colors.black.withValues(alpha: 0.10), blurRadius: 40, offset: const Offset(0, 8)),
|
||||
]),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
// 红色头部
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
color: red,
|
||||
child: Row(children: [
|
||||
const Text('MookNote', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, letterSpacing: 0.5)),
|
||||
const Spacer(),
|
||||
Text('收藏纪念\nCOLLECTIBLE', textAlign: TextAlign.right,
|
||||
style: TextStyle(fontSize: 8, color: Colors.white.withValues(alpha: 0.7), letterSpacing: 0.5, height: 1.5)),
|
||||
]),
|
||||
),
|
||||
|
||||
// 主体
|
||||
Padding(padding: const EdgeInsets.fromLTRB(24, 22, 24, 0), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
// 影院
|
||||
Row(children: [
|
||||
Icon(Icons.location_on_outlined, size: 14, color: red),
|
||||
const SizedBox(width: 4),
|
||||
Text(_cinemaCtrl.text, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF6B6B6B), letterSpacing: 0.3)),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
// 标题
|
||||
Text(movie.title, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: text, letterSpacing: 0.5, height: 1.15)),
|
||||
const SizedBox(height: 6),
|
||||
// 标签
|
||||
Wrap(spacing: 8, runSpacing: 6, children: [
|
||||
if (movie.genres.isNotEmpty) ...movie.genres.take(3).map((g) => _tagChip(g)),
|
||||
if (movie.releaseDate != null) _tagChip(_fmtDate(movie.releaseDate!)),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
// 三列网格
|
||||
Container(
|
||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: line, width: 0.5), bottom: BorderSide(color: line, width: 0.5))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
child: Row(children: [
|
||||
_gridCol('影厅', _hallCtrl.text, red),
|
||||
Container(width: 0.5, height: 36, color: const Color(0xFFF2EDE5)),
|
||||
_gridCol('座位', '${_seatRowCtrl.text}排${_seatNumCtrl.text}座', text),
|
||||
Container(width: 0.5, height: 36, color: const Color(0xFFF2EDE5)),
|
||||
_gridCol('上映', movie.releaseDate != null ? _fmtDate(movie.releaseDate!) : '--', red),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 海报图片
|
||||
if (hasPoster)
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
child: FadeInLocalImage(
|
||||
path: movie.posterPath,
|
||||
width: 320,
|
||||
height: 200,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
])),
|
||||
|
||||
// 内容区域
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题
|
||||
Text(
|
||||
movie.title,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
),
|
||||
// 撕票线(圆形缺口)
|
||||
_tearCircles(),
|
||||
|
||||
// 别名
|
||||
if (movie.alternateTitles.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
movie.alternateTitles.join(' / '),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: colors.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
// 底部信息
|
||||
Padding(padding: const EdgeInsets.fromLTRB(24, 10, 24, 0), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Container(width: 120, height: 120, decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFFE8E2D8), width: 0.5)),
|
||||
child: Image.asset('assets/images/ticket/qr.png', fit: BoxFit.contain,
|
||||
errorBuilder: (_, __, ___) => const SizedBox.shrink())),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: Column(children: [
|
||||
_collectInfo('票价', '¥${_priceCtrl.text}', isPrice: true),
|
||||
_collectInfo('服务费', '¥${_serviceFeeCtrl.text}'),
|
||||
_collectInfo('时间', _issueTimeCtrl.text),
|
||||
_collectInfo('平台', 'MookNote'),
|
||||
_collectInfo('编码', _ticketCodeCtrl.text, isCode: true),
|
||||
])),
|
||||
])),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 评分
|
||||
if (movie.rating != null && movie.rating! > 0) ...[
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.star,
|
||||
size: 18,
|
||||
color: Color(0xFFFFB800),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
movie.rating!.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFFFFB800),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'/ 10',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colors.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// 导演
|
||||
if (movie.directors.isNotEmpty)
|
||||
_buildInfoRow('导演', movie.directors.join(' / '), colors),
|
||||
|
||||
// 编剧
|
||||
if (movie.writers.isNotEmpty)
|
||||
_buildInfoRow('编剧', movie.writers.join(' / '), colors),
|
||||
|
||||
// 主演
|
||||
if (movie.actors.isNotEmpty)
|
||||
_buildInfoRow('主演', movie.actors.take(3).join(' / '), colors),
|
||||
|
||||
// 类型
|
||||
if (movie.genres.isNotEmpty)
|
||||
_buildInfoRow('类型', movie.genres.join(' / '), colors),
|
||||
|
||||
// 上映日期
|
||||
if (movie.releaseDate != null)
|
||||
_buildInfoRow(
|
||||
'上映',
|
||||
'${movie.releaseDate!.year}.${movie.releaseDate!.month.toString().padLeft(2, '0')}.${movie.releaseDate!.day.toString().padLeft(2, '0')}',
|
||||
colors,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 简介
|
||||
if (movie.summary != null && movie.summary!.isNotEmpty) ...[
|
||||
Text(
|
||||
'简介',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colors.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
movie.summary!,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colors.onSurface.withValues(alpha: 0.6),
|
||||
height: 1.6,
|
||||
),
|
||||
maxLines: 5,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 底部标识
|
||||
Divider(height: 1, color: colors.outline),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.movie_outlined,
|
||||
size: 14,
|
||||
color: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'来自 MookNote',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 底部条
|
||||
Padding(padding: const EdgeInsets.fromLTRB(24, 12, 24, 14), child: Row(children: [
|
||||
Text('MOOKNOTE MOVIE · TICKET', style: TextStyle(fontSize: 9, letterSpacing: 1, color: light.withValues(alpha: 0.7))),
|
||||
const Spacer(),
|
||||
Text(_ticketCodeCtrl.text, style: TextStyle(fontSize: 9, letterSpacing: 0.5, color: light.withValues(alpha: 0.7))),
|
||||
])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建信息行
|
||||
Widget _buildInfoRow(String label, String value, ColorScheme colors) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$label:',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colors.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colors.onSurface.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
Widget _tagChip(String text) {
|
||||
return Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(border: Border.all(color: const Color(0xFFEBE5D9), width: 0.5), borderRadius: BorderRadius.circular(3)),
|
||||
child: Text(text, style: const TextStyle(fontSize: 10, color: Color(0xFF6B6B6B), letterSpacing: 0.5)));
|
||||
}
|
||||
|
||||
/// 生成并分享海报
|
||||
Widget _gridCol(String label, String value, Color valueColor) {
|
||||
return Expanded(child: Column(children: [
|
||||
Text(label, style: const TextStyle(fontSize: 9, color: Color(0xFF8C8C8C), letterSpacing: 1)),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, textAlign: TextAlign.center, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: valueColor, letterSpacing: 0.5)),
|
||||
]));
|
||||
}
|
||||
|
||||
Widget _collectInfo(String label, String value, {bool isPrice = false, bool isCode = false}) {
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 3), child: Row(children: [
|
||||
SizedBox(width: 32, child: Text(label, style: const TextStyle(fontSize: 9, color: Color(0xFF8C8C8C), letterSpacing: 0.5))),
|
||||
Expanded(child: Text(value, textAlign: TextAlign.right, style: TextStyle(
|
||||
fontSize: isPrice ? 15 : 10,
|
||||
fontWeight: isPrice ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isPrice ? const Color(0xFFC0392B) : const Color(0xFF1A1A1A).withValues(alpha: 0.8),
|
||||
fontFamily: isCode ? 'monospace' : null,
|
||||
letterSpacing: isCode ? 0.5 : 0,
|
||||
))),
|
||||
]));
|
||||
}
|
||||
|
||||
// ─── 通用组件 ───
|
||||
|
||||
Widget _dashedLine(Color color) {
|
||||
return Padding(padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: CustomPaint(size: const Size(double.infinity, 1), painter: _DashedLinePainter(color: color)));
|
||||
}
|
||||
|
||||
Widget _tearScissors(Color bg) {
|
||||
return SizedBox(height: 28, child: Stack(children: [
|
||||
Positioned(left: 20, right: 20, top: 13, child: CustomPaint(
|
||||
size: const Size(double.infinity, 1), painter: _DashedLinePainter(color: const Color(0xFFC8C4B8), dashWidth: 6, dashSpace: 8))),
|
||||
Positioned(left: 2, top: 4, child: Icon(Icons.content_cut, size: 16, color: const Color(0xFF999999))),
|
||||
]));
|
||||
}
|
||||
|
||||
Widget _tearCircles() {
|
||||
return Padding(padding: const EdgeInsets.symmetric(horizontal: 24), child: SizedBox(height: 24, child: Stack(children: [
|
||||
Positioned(left: 0, right: 0, top: 11, child: CustomPaint(
|
||||
size: const Size(double.infinity, 1), painter: _DashedLinePainter(color: const Color(0xFFD8D0C0), dashWidth: 9, dashSpace: 9))),
|
||||
Positioned(left: -30, top: 5.5, child: Container(width: 13, height: 13, decoration: BoxDecoration(color: const Color(0xFFEBE5DB), shape: BoxShape.circle))),
|
||||
Positioned(right: -30, top: 5.5, child: Container(width: 13, height: 13, decoration: BoxDecoration(color: const Color(0xFFEBE5DB), shape: BoxShape.circle))),
|
||||
])));
|
||||
}
|
||||
|
||||
String _fmtDate(DateTime d) => '${d.year}.${d.month.toString().padLeft(2, '0')}.${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
String _subtitleText(Movie m) {
|
||||
final parts = <String>[];
|
||||
if (m.genres.isNotEmpty) parts.add(m.genres.join(' / '));
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
String _statusEN(String s) {
|
||||
switch (s) { case 'watched': return 'ADMITTED'; case 'watching': return 'NOW PLAYING'; default: return 'RESERVED'; }
|
||||
}
|
||||
|
||||
// ─── 生成分享 ───
|
||||
|
||||
Future<void> _generateAndShare() async {
|
||||
setState(() => _isGenerating = true);
|
||||
|
||||
try {
|
||||
// 捕获海报图片
|
||||
final boundary = _posterKey.currentContext?.findRenderObject()
|
||||
as RenderRepaintBoundary?;
|
||||
if (boundary == null) {
|
||||
throw Exception('无法获取海报边界');
|
||||
}
|
||||
|
||||
final boundary = _posterKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
|
||||
if (boundary == null) throw Exception('无法获取海报边界');
|
||||
final image = await boundary.toImage(pixelRatio: 3.0);
|
||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
if (byteData == null) {
|
||||
throw Exception('无法生成图片数据');
|
||||
}
|
||||
|
||||
final bytes = byteData.buffer.asUint8List();
|
||||
|
||||
// 保存到临时文件
|
||||
if (byteData == null) throw Exception('无法生成图片数据');
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final fileName = 'movie_poster_${DateTime.now().millisecondsSinceEpoch}.png';
|
||||
final file = File('${tempDir.path}/$fileName');
|
||||
await file.writeAsBytes(bytes);
|
||||
|
||||
// 分享
|
||||
await Share.shareXFiles(
|
||||
[XFile(file.path)],
|
||||
text: '分享影视:${widget.movie.title}',
|
||||
);
|
||||
final file = File('${tempDir.path}/movie_poster_${DateTime.now().millisecondsSinceEpoch}.png');
|
||||
await file.writeAsBytes(byteData.buffer.asUint8List());
|
||||
await Share.shareXFiles([XFile(file.path)], text: '分享影视:${widget.movie.title}');
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ToastUtil.show(context, '生成海报失败:$e');
|
||||
}
|
||||
if (mounted) ToastUtil.show(context, '生成海报失败:$e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isGenerating = false);
|
||||
}
|
||||
if (mounted) setState(() => _isGenerating = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DashedLinePainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double dashWidth;
|
||||
final double dashSpace;
|
||||
_DashedLinePainter({required this.color, this.dashWidth = 4, this.dashSpace = 4});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color..strokeWidth = 1..style = PaintingStyle.stroke;
|
||||
double x = 0;
|
||||
while (x < size.width) { canvas.drawLine(Offset(x, 0), Offset(x + dashWidth, 0), paint); x += dashWidth + dashSpace; }
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
|
||||
/// 阅读状态选择栏 - 水滴滑动动画
|
||||
/// 阅读状态选择栏 - 交叉渐变动画
|
||||
class BookStatusBar extends StatelessWidget {
|
||||
const BookStatusBar({super.key});
|
||||
|
||||
@@ -15,9 +15,7 @@ class BookStatusBar extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(color: colors.outline, width: 0.5),
|
||||
),
|
||||
border: Border(bottom: BorderSide(color: colors.outline, width: 0.5)),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
@@ -25,66 +23,13 @@ class BookStatusBar extends StatelessWidget {
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final tabWidth = constraints.maxWidth / 3;
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
curve: Curves.elasticOut,
|
||||
left: provider.bookStatusIndex * tabWidth,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: tabWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primary,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '已读',
|
||||
icon: Icons.check_circle_outline,
|
||||
isSelected: provider.bookStatusIndex == 0,
|
||||
onTap: () => provider.setBookStatusIndex(0),
|
||||
),
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '在读',
|
||||
icon: Icons.menu_book_outlined,
|
||||
isSelected: provider.bookStatusIndex == 1,
|
||||
onTap: () => provider.setBookStatusIndex(1),
|
||||
),
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '想读',
|
||||
icon: Icons.bookmark_outlined,
|
||||
isSelected: provider.bookStatusIndex == 2,
|
||||
onTap: () => provider.setBookStatusIndex(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: Row(children: [
|
||||
_buildTab(colors, '已读', Icons.check_circle_outline, provider.bookStatusIndex == 0, () => provider.setBookStatusIndex(0)),
|
||||
_buildTab(colors, '在读', Icons.menu_book_outlined, provider.bookStatusIndex == 1, () => provider.setBookStatusIndex(1)),
|
||||
_buildTab(colors, '想读', Icons.bookmark_outlined, provider.bookStatusIndex == 2, () => provider.setBookStatusIndex(2)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -92,39 +37,45 @@ class BookStatusBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({
|
||||
required ColorScheme colors,
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
Widget _buildTab(ColorScheme colors, String label, IconData icon, bool isSelected, VoidCallback onTap) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
margin: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? colors.primary : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: isSelected ? [BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2))] : null,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
child: Icon(icon, key: ValueKey(isSelected), size: 16,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6)),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
child: Text(label, key: ValueKey('$label$isSelected')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
|
||||
/// 观影状态选择栏 - 水滴滑动动画
|
||||
/// 观影状态选择栏 - 交叉渐变动画
|
||||
class MovieStatusBar extends StatelessWidget {
|
||||
const MovieStatusBar({super.key});
|
||||
|
||||
@@ -15,9 +15,7 @@ class MovieStatusBar extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(color: colors.outline, width: 0.5),
|
||||
),
|
||||
border: Border(bottom: BorderSide(color: colors.outline, width: 0.5)),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
@@ -25,66 +23,13 @@ class MovieStatusBar extends StatelessWidget {
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final tabWidth = constraints.maxWidth / 3;
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
curve: Curves.elasticOut,
|
||||
left: provider.movieStatusIndex * tabWidth,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: tabWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primary,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '已看',
|
||||
icon: Icons.check_circle_outline,
|
||||
isSelected: provider.movieStatusIndex == 0,
|
||||
onTap: () => provider.setMovieStatusIndex(0),
|
||||
),
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '在看',
|
||||
icon: Icons.play_circle_outline,
|
||||
isSelected: provider.movieStatusIndex == 1,
|
||||
onTap: () => provider.setMovieStatusIndex(1),
|
||||
),
|
||||
_buildTab(
|
||||
colors: colors,
|
||||
label: '想看',
|
||||
icon: Icons.bookmark_outline,
|
||||
isSelected: provider.movieStatusIndex == 2,
|
||||
onTap: () => provider.setMovieStatusIndex(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: Row(children: [
|
||||
_buildTab(colors, '已看', Icons.check_circle_outline, provider.movieStatusIndex == 0, () => provider.setMovieStatusIndex(0)),
|
||||
_buildTab(colors, '在看', Icons.play_circle_outline, provider.movieStatusIndex == 1, () => provider.setMovieStatusIndex(1)),
|
||||
_buildTab(colors, '想看', Icons.bookmark_outline, provider.movieStatusIndex == 2, () => provider.setMovieStatusIndex(2)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -92,39 +37,45 @@ class MovieStatusBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({
|
||||
required ColorScheme colors,
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
Widget _buildTab(ColorScheme colors, String label, IconData icon, bool isSelected, VoidCallback onTap) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
margin: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? colors.primary : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: isSelected ? [BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2))] : null,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
child: Icon(icon, key: ValueKey(isSelected), size: 16,
|
||||
color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6)),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
child: Text(label, key: ValueKey('$label$isSelected')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
246
pubspec.lock
246
pubspec.lock
@@ -6,7 +6,7 @@ packages:
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.6.1"
|
||||
args:
|
||||
@@ -14,7 +14,7 @@ packages:
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
@@ -22,7 +22,7 @@ packages:
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
@@ -30,7 +30,7 @@ packages:
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
@@ -38,7 +38,7 @@ packages:
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
checked_yaml:
|
||||
@@ -46,7 +46,7 @@ packages:
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_util:
|
||||
@@ -54,7 +54,7 @@ packages:
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
@@ -62,7 +62,7 @@ packages:
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
@@ -70,7 +70,7 @@ packages:
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
collection:
|
||||
@@ -78,7 +78,7 @@ packages:
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
@@ -86,7 +86,7 @@ packages:
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
@@ -94,7 +94,7 @@ packages:
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
@@ -102,7 +102,7 @@ packages:
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
equatable:
|
||||
@@ -110,7 +110,7 @@ packages:
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
extended_text_field:
|
||||
@@ -118,7 +118,7 @@ packages:
|
||||
description:
|
||||
name: extended_text_field
|
||||
sha256: "3996195c117c6beb734026a7bc0ba80d7e4e84e4edd4728caa544d8209ab4d7d"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "16.0.2"
|
||||
extended_text_library:
|
||||
@@ -126,7 +126,7 @@ packages:
|
||||
description:
|
||||
name: extended_text_library
|
||||
sha256: "13d99f8a10ead472d5e2cf4770d3d047203fe5054b152e9eb5dc692a71befbba"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "12.0.1"
|
||||
fake_async:
|
||||
@@ -134,7 +134,7 @@ packages:
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
@@ -142,7 +142,7 @@ packages:
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
@@ -150,7 +150,7 @@ packages:
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_picker:
|
||||
@@ -158,7 +158,7 @@ packages:
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "8.3.7"
|
||||
file_selector_linux:
|
||||
@@ -166,7 +166,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
@@ -174,7 +174,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
@@ -182,7 +182,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
file_selector_windows:
|
||||
@@ -190,7 +190,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
fixnum:
|
||||
@@ -198,7 +198,7 @@ packages:
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
fl_chart:
|
||||
@@ -206,7 +206,7 @@ packages:
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.69.2"
|
||||
flutter:
|
||||
@@ -219,7 +219,7 @@ packages:
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_lints:
|
||||
@@ -227,7 +227,7 @@ packages:
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_localizations:
|
||||
@@ -240,7 +240,7 @@ packages:
|
||||
description:
|
||||
name: flutter_markdown_plus
|
||||
sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.7"
|
||||
flutter_plugin_android_lifecycle:
|
||||
@@ -248,7 +248,7 @@ packages:
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.35"
|
||||
flutter_staggered_grid_view:
|
||||
@@ -256,7 +256,7 @@ packages:
|
||||
description:
|
||||
name: flutter_staggered_grid_view
|
||||
sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_test:
|
||||
@@ -274,7 +274,7 @@ packages:
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
http:
|
||||
@@ -282,7 +282,7 @@ packages:
|
||||
description:
|
||||
name: http
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http_parser:
|
||||
@@ -290,7 +290,7 @@ packages:
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
@@ -298,7 +298,7 @@ packages:
|
||||
description:
|
||||
name: image
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
image_picker:
|
||||
@@ -306,7 +306,7 @@ packages:
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
image_picker_android:
|
||||
@@ -314,7 +314,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.13+17"
|
||||
image_picker_for_web:
|
||||
@@ -322,7 +322,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
image_picker_ios:
|
||||
@@ -330,7 +330,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
image_picker_linux:
|
||||
@@ -338,7 +338,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
@@ -346,7 +346,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
image_picker_platform_interface:
|
||||
@@ -354,7 +354,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.11.1"
|
||||
image_picker_windows:
|
||||
@@ -362,7 +362,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
intl:
|
||||
@@ -370,7 +370,7 @@ packages:
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
jni:
|
||||
@@ -378,7 +378,7 @@ packages:
|
||||
description:
|
||||
name: jni
|
||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
jni_flutter:
|
||||
@@ -386,7 +386,7 @@ packages:
|
||||
description:
|
||||
name: jni_flutter
|
||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
json_annotation:
|
||||
@@ -394,7 +394,7 @@ packages:
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
leak_tracker:
|
||||
@@ -402,7 +402,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
@@ -410,7 +410,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
@@ -418,7 +418,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
@@ -426,7 +426,7 @@ packages:
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
logging:
|
||||
@@ -434,7 +434,7 @@ packages:
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
markdown:
|
||||
@@ -442,7 +442,7 @@ packages:
|
||||
description:
|
||||
name: markdown
|
||||
sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.3.1"
|
||||
matcher:
|
||||
@@ -450,7 +450,7 @@ packages:
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
@@ -458,7 +458,7 @@ packages:
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
@@ -466,7 +466,7 @@ packages:
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
@@ -474,7 +474,7 @@ packages:
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
nested:
|
||||
@@ -482,7 +482,7 @@ packages:
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objective_c:
|
||||
@@ -490,7 +490,7 @@ packages:
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.4.1"
|
||||
package_config:
|
||||
@@ -498,7 +498,7 @@ packages:
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
package_info_plus:
|
||||
@@ -506,7 +506,7 @@ packages:
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "8.3.1"
|
||||
package_info_plus_platform_interface:
|
||||
@@ -514,7 +514,7 @@ packages:
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
path:
|
||||
@@ -522,7 +522,7 @@ packages:
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
@@ -530,7 +530,7 @@ packages:
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
path_provider_android:
|
||||
@@ -538,7 +538,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
path_provider_foundation:
|
||||
@@ -546,7 +546,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
@@ -554,7 +554,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
@@ -562,7 +562,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
path_provider_windows:
|
||||
@@ -570,7 +570,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
@@ -578,7 +578,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "11.4.0"
|
||||
permission_handler_android:
|
||||
@@ -586,7 +586,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "12.1.0"
|
||||
permission_handler_apple:
|
||||
@@ -594,7 +594,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.4.10"
|
||||
permission_handler_html:
|
||||
@@ -602,7 +602,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.3+5"
|
||||
permission_handler_platform_interface:
|
||||
@@ -610,7 +610,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
permission_handler_windows:
|
||||
@@ -618,7 +618,7 @@ packages:
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
@@ -626,7 +626,7 @@ packages:
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
@@ -634,7 +634,7 @@ packages:
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
@@ -642,7 +642,7 @@ packages:
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
provider:
|
||||
@@ -650,7 +650,7 @@ packages:
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
@@ -658,7 +658,7 @@ packages:
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
record_use:
|
||||
@@ -666,7 +666,7 @@ packages:
|
||||
description:
|
||||
name: record_use
|
||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
share_plus:
|
||||
@@ -674,7 +674,7 @@ packages:
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.1.4"
|
||||
share_plus_platform_interface:
|
||||
@@ -682,7 +682,7 @@ packages:
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
shared_preferences:
|
||||
@@ -690,7 +690,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
shared_preferences_android:
|
||||
@@ -698,7 +698,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.23"
|
||||
shared_preferences_foundation:
|
||||
@@ -706,7 +706,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
@@ -714,7 +714,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
@@ -722,7 +722,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_web:
|
||||
@@ -730,7 +730,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
@@ -738,7 +738,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
@@ -751,7 +751,7 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
sqflite:
|
||||
@@ -759,7 +759,7 @@ packages:
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.2+1"
|
||||
sqflite_android:
|
||||
@@ -767,7 +767,7 @@ packages:
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.2+3"
|
||||
sqflite_common:
|
||||
@@ -775,7 +775,7 @@ packages:
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.8"
|
||||
sqflite_darwin:
|
||||
@@ -783,7 +783,7 @@ packages:
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
sqflite_platform_interface:
|
||||
@@ -791,7 +791,7 @@ packages:
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
stack_trace:
|
||||
@@ -799,7 +799,7 @@ packages:
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
@@ -807,7 +807,7 @@ packages:
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
@@ -815,7 +815,7 @@ packages:
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
@@ -823,7 +823,7 @@ packages:
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.4.0+1"
|
||||
term_glyph:
|
||||
@@ -831,7 +831,7 @@ packages:
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
@@ -839,7 +839,7 @@ packages:
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
typed_data:
|
||||
@@ -847,7 +847,7 @@ packages:
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
@@ -855,7 +855,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
@@ -863,7 +863,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.30"
|
||||
url_launcher_ios:
|
||||
@@ -871,7 +871,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
url_launcher_linux:
|
||||
@@ -879,7 +879,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
@@ -887,7 +887,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
@@ -895,7 +895,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
@@ -903,7 +903,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
url_launcher_windows:
|
||||
@@ -911,7 +911,7 @@ packages:
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
@@ -919,7 +919,7 @@ packages:
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
vector_math:
|
||||
@@ -927,7 +927,7 @@ packages:
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
@@ -935,7 +935,7 @@ packages:
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
web:
|
||||
@@ -943,7 +943,7 @@ packages:
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
webview_flutter:
|
||||
@@ -951,7 +951,7 @@ packages:
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: e4d3474277c5043f5f3100ed27d94ad693abfd5c602b0221c719a4497e4ba1e4
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.14.0"
|
||||
webview_flutter_android:
|
||||
@@ -959,7 +959,7 @@ packages:
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
webview_flutter_platform_interface:
|
||||
@@ -967,7 +967,7 @@ packages:
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.15.1"
|
||||
webview_flutter_wkwebview:
|
||||
@@ -975,7 +975,7 @@ packages:
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.25.1"
|
||||
win32:
|
||||
@@ -983,7 +983,7 @@ packages:
|
||||
description:
|
||||
name: win32
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.15.0"
|
||||
xdg_directories:
|
||||
@@ -991,7 +991,7 @@ packages:
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
@@ -999,7 +999,7 @@ packages:
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
@@ -1007,7 +1007,7 @@ packages:
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
|
||||
@@ -53,3 +53,4 @@ flutter:
|
||||
assets:
|
||||
- assets/images/
|
||||
- assets/icon/
|
||||
- assets/images/ticket/
|
||||
|
||||
BIN
参考/票根html/maoyan.png
Normal file
BIN
参考/票根html/maoyan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
参考/票根html/mookiletter.png
Normal file
BIN
参考/票根html/mookiletter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
395
参考/票根html/票根.html
Normal file
395
参考/票根html/票根.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>猫眼电影 · 票根</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700;900&family=Noto+Serif+SC:wght@400;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--maoyan-red: #E5342F;
|
||||
--maoyan-red-dark: #C41F1A;
|
||||
--ticket-bg: #FFFDF7;
|
||||
--text-primary: #1A1A1A;
|
||||
--text-secondary: #666666;
|
||||
--text-muted: #999999;
|
||||
--divider: #E8E4DA;
|
||||
--bg-dark: #1E1610;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #F5F0E6;
|
||||
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* ── Ticket Wrapper ── */
|
||||
.ticket-wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
filter: drop-shadow(0 4px 24px rgba(0,0,0,0.12));
|
||||
transition: transform 0.3s ease;
|
||||
animation: ticketEntrance 0.6s cubic-bezier(0.34,1.56,0.64,1) forwards;
|
||||
}
|
||||
|
||||
.ticket-wrapper:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
@keyframes ticketEntrance {
|
||||
0% { opacity: 0; transform: translateY(30px) scale(0.96); }
|
||||
100% { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
/* ── Main Ticket ── */
|
||||
.ticket {
|
||||
background: var(--ticket-bg);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 0 0 rgba(255,255,255,0.06) inset;
|
||||
}
|
||||
|
||||
/* Subtle print texture */
|
||||
.ticket::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 1px,
|
||||
rgba(0,0,0,0.003) 1px,
|
||||
rgba(0,0,0,0.003) 2px
|
||||
);
|
||||
border-radius: 12px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* ── Header (票头) ── */
|
||||
.ticket-header {
|
||||
padding: 18px 24px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #F0EDE5;
|
||||
}
|
||||
|
||||
.maoyan-logo {
|
||||
width: 42px; height: 42px;
|
||||
flex-shrink: 0;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px; font-weight: 900;
|
||||
color: #1A1A1A;
|
||||
letter-spacing: 0.06em;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
|
||||
/* ── Body (票身) ── */
|
||||
.ticket-body {
|
||||
padding: 22px 24px 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Cinema */
|
||||
.cinema-info {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cinema-name {
|
||||
font-size: 15px; font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cinema-address {
|
||||
font-size: 12px; color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Movie title */
|
||||
.movie-title-section {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.movie-title {
|
||||
font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif;
|
||||
font-size: 28px; font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3; letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.movie-subtitle {
|
||||
font-size: 13px; color: var(--text-secondary);
|
||||
margin-top: 3px; letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
/* Hall & seat */
|
||||
.session-info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hall-badge {
|
||||
display: inline-flex;
|
||||
align-items: center; gap: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px; font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.hall-badge svg {
|
||||
width: 16px; height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.seat-datetime {
|
||||
font-size: 14px; color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
display: flex; align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #D0CCC0; font-weight: 300;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.datetime-text {
|
||||
color: var(--text-secondary); font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── Tear Line (撕票虚线) ── */
|
||||
.tear-line {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 12px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tear-line::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 18px; right: 18px;
|
||||
top: 50%; transform: translateY(-50%);
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#C8C4B8 0px, #C8C4B8 6px,
|
||||
transparent 6px, transparent 14px
|
||||
);
|
||||
}
|
||||
|
||||
.scissors-icon {
|
||||
position: absolute;
|
||||
left: 0; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 14px; color: #B8B4A8;
|
||||
z-index: 1;
|
||||
background: var(--ticket-bg);
|
||||
padding-right: 6px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ── Footer (票尾) ── */
|
||||
.ticket-footer {
|
||||
padding: 10px 24px 16px;
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* QR code */
|
||||
.qr-section {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qr-label {
|
||||
font-size: 10px; color: var(--text-muted);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Detail rows */
|
||||
.details-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 18px; font-weight: 700;
|
||||
color: var(--maoyan-red);
|
||||
}
|
||||
|
||||
.price-symbol {
|
||||
font-size: 12px; font-weight: 500;
|
||||
}
|
||||
|
||||
.service-fee {
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.platform-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.platform-text {
|
||||
font-size: 11px; color: var(--text-muted);
|
||||
letter-spacing: 0.05em; font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 440px) {
|
||||
.ticket-wrapper { max-width: 100%; }
|
||||
.ticket-header { padding: 14px 18px 12px; }
|
||||
.ticket-body { padding: 16px 18px 14px; }
|
||||
.ticket-footer { padding: 10px 18px 14px; gap: 12px; }
|
||||
.movie-title { font-size: 24px; }
|
||||
.qr-code { width: 110px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="ticket-wrapper">
|
||||
<div class="ticket">
|
||||
|
||||
<!-- ═══════════ HEADER ═══════════ -->
|
||||
<div class="ticket-header">
|
||||
<div class="maoyan-logo">
|
||||
<img src="maoyan.png" alt="猫眼电影" style="width:100%;height:100%;object-fit:contain;">
|
||||
</div>
|
||||
<div class="logo-text">猫眼电影</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ BODY ═══════════ -->
|
||||
<div class="ticket-body">
|
||||
<div class="cinema-info">
|
||||
<div class="cinema-name">万达影城(朝阳大悦城IMAX店)</div>
|
||||
</div>
|
||||
|
||||
<div class="movie-title-section">
|
||||
<div class="movie-title">流浪地球3</div>
|
||||
<div class="movie-subtitle">科幻 / 冒险 / 3D IMAX · 片长148分钟</div>
|
||||
</div>
|
||||
|
||||
<div class="session-info">
|
||||
<div class="hall-badge">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="2" width="20" height="8" rx="1"/>
|
||||
<path d="M4 10v10h16V10"/>
|
||||
<path d="M6 14h4v6H6z"/><path d="M14 14h4v6h-4z"/>
|
||||
</svg>
|
||||
3号IMAX厅
|
||||
</div>
|
||||
</div>
|
||||
<div class="seat-datetime">
|
||||
<span>8排12座</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="datetime-text">2026年6月18日 19:30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ TEAR LINE ═══════════ -->
|
||||
<div class="tear-line">
|
||||
<span class="scissors-icon">✂</span>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ FOOTER ═══════════ -->
|
||||
<div class="ticket-footer">
|
||||
<div class="qr-section">
|
||||
<div class="qr-code">
|
||||
<img src="mookiletter.png"
|
||||
alt="验票二维码" style="width:100%;height:100%;object-fit:contain;">
|
||||
</div>
|
||||
<span class="qr-label"></span>
|
||||
</div>
|
||||
|
||||
<div class="details-section">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">票价</span>
|
||||
<span class="detail-value price-value"><span class="price-symbol">¥</span>49.90</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">服务费</span>
|
||||
<span class="detail-value service-fee">¥5.00</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">出票时间</span>
|
||||
<span class="detail-value">2026-06-18 10:32:15</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">淘票票</span>
|
||||
<span class="detail-value platform-badge">
|
||||
<span class="platform-text">已出票</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">编码</span>
|
||||
<span class="detail-value" style="font-family: monospace; letter-spacing:0.05em;">8602 3100 0618 7251</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
444
参考/票根html/票根收藏版.html
Normal file
444
参考/票根html/票根收藏版.html
Normal file
@@ -0,0 +1,444 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>猫眼电影 · 票根(收藏版)</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700;900&family=Noto+Serif+SC:wght@400;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--red: #D4342E;
|
||||
--gold: #C9A96E;
|
||||
--bg: #F8F5F0;
|
||||
--card: #FFFFFF;
|
||||
--text: #1C1C1C;
|
||||
--text-secondary: #6B6B6B;
|
||||
--text-muted: #A0A0A0;
|
||||
--border: #EBE5D9;
|
||||
--divider: #EEE8DA;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(160deg, #EBE4D8 0%, #F2EDE4 40%, #EEF0EF 100%);
|
||||
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
/* Ambient */
|
||||
body::before, body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.15;
|
||||
}
|
||||
body::before {
|
||||
width: 420px; height: 420px;
|
||||
background: radial-gradient(circle, #D4342E 0%, transparent 70%);
|
||||
top: -120px; right: -120px;
|
||||
}
|
||||
body::after {
|
||||
width: 300px; height: 300px;
|
||||
background: radial-gradient(circle, #C9A96E 0%, transparent 70%);
|
||||
bottom: -80px; left: -80px;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 400px;
|
||||
background: var(--card);
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 1px 0 rgba(0,0,0,0.03),
|
||||
0 2px 8px rgba(0,0,0,0.04),
|
||||
0 12px 40px rgba(0,0,0,0.10);
|
||||
overflow: hidden;
|
||||
animation: cardIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes cardIn {
|
||||
0% { opacity: 0; transform: translateY(40px) scale(0.94); }
|
||||
100% { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--red) 0%, #E85D59 30%, var(--red) 60%, #C1272D 100%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
padding: 28px 28px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 34px; height: 34px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.logo img {
|
||||
width: 100%; height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 20px; font-weight: 800;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
font-size: 10px; color: var(--text-muted);
|
||||
text-align: right;
|
||||
letter-spacing: 0.05em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.body {
|
||||
padding: 24px 28px 0;
|
||||
}
|
||||
|
||||
.cinema {
|
||||
font-size: 13px; font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 18px;
|
||||
letter-spacing: 0.03em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.cinema svg {
|
||||
width: 14px; height: 14px;
|
||||
flex-shrink: 0;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.movie-title {
|
||||
font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif;
|
||||
font-size: 34px; font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.15;
|
||||
letter-spacing: 0.06em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.movie-meta-row {
|
||||
font-size: 12px; color: var(--text-muted);
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.movie-meta-row .tag {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Session grid */
|
||||
.session-grid {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 16px 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.session-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.session-item .label {
|
||||
font-size: 10px; color: var(--text-muted);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.session-item .value {
|
||||
font-size: 13px; font-weight: 600;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.session-item .value.large {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Tear line */
|
||||
.tear-section {
|
||||
padding: 0 28px;
|
||||
}
|
||||
.tear-line {
|
||||
position: relative;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tear-line::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
top: 50%;
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#D5CFC0 0px, #D5CFC0 7px,
|
||||
transparent 7px, transparent 15px
|
||||
);
|
||||
}
|
||||
.tear-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
.tear-dot.left { left: -2px; }
|
||||
.tear-dot.right { right: -2px; }
|
||||
.tear-dot svg {
|
||||
width: 12px; height: 12px;
|
||||
color: #C5BFAE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
padding: 10px 28px 28px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
font-size: 12px;
|
||||
}
|
||||
.info-row .key {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.info-row .val {
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
.info-row .val.price {
|
||||
font-size: 22px; font-weight: 700;
|
||||
color: var(--red);
|
||||
}
|
||||
.info-row .val.code {
|
||||
font-family: 'SF Mono', 'Cascadia Code', 'Courier New', monospace;
|
||||
font-size: 11px; letter-spacing: 0.06em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
|
||||
/* QR */
|
||||
.qr-wrap {
|
||||
flex-shrink: 0;
|
||||
width: 150px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.qr-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.qr-img img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Bottom strip */
|
||||
.footer-strip {
|
||||
margin: 0 28px;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid var(--divider);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.footer-strip .left {
|
||||
font-size: 10px; color: var(--text-muted);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.footer-strip .right {
|
||||
font-size: 10px; color: var(--text-muted);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Print */
|
||||
@media print {
|
||||
@page { margin: 14mm; size: auto; }
|
||||
body {
|
||||
background: #FFF;
|
||||
padding: 0;
|
||||
}
|
||||
body::before, body::after { display: none; }
|
||||
.card {
|
||||
box-shadow: none;
|
||||
border: 1px solid #D8D3C5;
|
||||
border-radius: 10px;
|
||||
animation: none;
|
||||
width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card::before { height: 3px; }
|
||||
.header { padding: 22px 24px 0; }
|
||||
.body { padding: 18px 24px 0; }
|
||||
.tear-section { padding: 0 24px; }
|
||||
.footer { padding: 8px 24px 24px; }
|
||||
.footer-strip { margin: 0 24px; }
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.card { width: 100%; max-width: 400px; }
|
||||
.header { padding: 22px 20px 0; }
|
||||
.body { padding: 18px 20px 0; }
|
||||
.tear-section { padding: 0 20px; }
|
||||
.footer { padding: 8px 20px 24px; gap: 14px; }
|
||||
.footer-strip { margin: 0 20px; }
|
||||
.movie-title { font-size: 28px; }
|
||||
.session-grid { gap: 14px; }
|
||||
.qr-img { width: 120px; height: auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="logo">
|
||||
<img src="maoyan.png" alt="">
|
||||
</div>
|
||||
<span class="brand">猫眼电影</span>
|
||||
</div>
|
||||
<div class="header-right">收藏纪念<br>PASSENGER COPY</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<div class="cinema">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
万达影城(朝阳大悦城IMAX店)
|
||||
</div>
|
||||
|
||||
<div class="movie-title">流浪地球3</div>
|
||||
<div class="movie-meta-row">
|
||||
<span class="tag">3D IMAX</span>
|
||||
<span class="tag">科幻 / 冒险</span>
|
||||
<span class="tag">片长148分钟</span>
|
||||
</div>
|
||||
|
||||
<div class="session-grid">
|
||||
<div class="session-item">
|
||||
<span class="label">影厅</span>
|
||||
<span class="value large">3号IMAX厅</span>
|
||||
</div>
|
||||
<div class="session-item">
|
||||
<span class="label">座位</span>
|
||||
<span class="value large">8排12座</span>
|
||||
</div>
|
||||
<div class="session-item">
|
||||
<span class="label">时间</span>
|
||||
<span class="value">2026/06/18 19:30</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tear-section">
|
||||
<div class="tear-line">
|
||||
<span class="tear-dot left">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>
|
||||
</span>
|
||||
<span class="tear-dot right">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="qr-wrap">
|
||||
<div class="qr-img">
|
||||
<img src="mookiletter.png" alt="二维码">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-right">
|
||||
<div class="info-row">
|
||||
<span class="key">票价</span>
|
||||
<span class="val price">¥49.90</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="key">服务费</span>
|
||||
<span class="val">¥5.00</span>
|
||||
</div>
|
||||
<div class="divider-thin"></div>
|
||||
<div class="info-row">
|
||||
<span class="key">出票时间</span>
|
||||
<span class="val">2026-06-18 10:32</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="key">淘票票</span>
|
||||
<span class="val">已出票</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="key">编码</span>
|
||||
<span class="val code">8602 3100 0618 7251</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-strip">
|
||||
<span class="left">M AOYAN MOVIE · TICKET</span>
|
||||
<span class="right">MY202606181930</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
386
参考/票根html/票根样式三.html
Normal file
386
参考/票根html/票根样式三.html
Normal file
@@ -0,0 +1,386 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>电影票根 · 收藏版</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700;900&family=Noto+Serif+SC:wght@400;600;700&family=DM+Serif+Display:ital@0;1&display=swap');
|
||||
|
||||
:root {
|
||||
--bg: #FAF7F2;
|
||||
--card: #FFFFFF;
|
||||
--red: #C0392B;
|
||||
--red-light: #E74C3C;
|
||||
--text: #1A1A1A;
|
||||
--text-mid: #555555;
|
||||
--text-light: #8C8C8C;
|
||||
--line: #E8E2D8;
|
||||
--line-light: #F2EDE5;
|
||||
--stamp: #E8D5C4;
|
||||
--accent: #2C5F8A;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #EBE5DB;
|
||||
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.ticket {
|
||||
width: 420px;
|
||||
background: #FFFFFB;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0,0,0,0.04),
|
||||
0 2px 12px rgba(0,0,0,0.05),
|
||||
0 8px 40px rgba(0,0,0,0.10);
|
||||
animation: in 0.65s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes in {
|
||||
0% { opacity: 0; transform: translateY(32px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Red header strip ── */
|
||||
.hdr {
|
||||
background: var(--red);
|
||||
padding: 14px 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.hdr-title {
|
||||
font-size: 18px; font-weight: 700;
|
||||
color: #FFF;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.hdr-sub {
|
||||
margin-left: auto;
|
||||
font-size: 9px; color: rgba(255,255,255,0.7);
|
||||
text-align: right;
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ── Main content ── */
|
||||
.main {
|
||||
padding: 26px 32px 0;
|
||||
}
|
||||
|
||||
.cinema-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.cinema-icon {
|
||||
width: 18px; height: 18px;
|
||||
flex-shrink: 0;
|
||||
color: var(--red);
|
||||
}
|
||||
.cinema-name {
|
||||
font-size: 13px; font-weight: 500;
|
||||
color: var(--text-mid);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.film-title {
|
||||
font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif;
|
||||
font-size: 38px; font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.12;
|
||||
letter-spacing: 0.06em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.film-sub {
|
||||
font-size: 12px; color: var(--text-light);
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 26px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.film-sub span {
|
||||
display: inline-block;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* ── Three-column session ── */
|
||||
.sess {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--line);
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 16px 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.sess-col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-right: 1px solid var(--line-light);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.sess-col:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.sess-col .sess-label {
|
||||
font-size: 9px; color: var(--text-light);
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
display: block;
|
||||
}
|
||||
.sess-col .sess-val {
|
||||
font-size: 15px; font-weight: 600;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sess-col .sess-val.hi {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
/* ── Tear ── */
|
||||
.tear {
|
||||
margin: 0 32px;
|
||||
height: 28px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tear::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
top: 50%;
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#D8D0C0 0px, #D8D0C0 9px,
|
||||
transparent 9px, transparent 18px
|
||||
);
|
||||
}
|
||||
.tear-circle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 13px; height: 13px;
|
||||
background: #EBE5DB;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tear-circle.l { left: -38px; }
|
||||
.tear-circle.r { right: -38px; }
|
||||
|
||||
/* ── Footer ── */
|
||||
.ftr {
|
||||
padding: 14px 32px 26px;
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ftr-l {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.qr {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.qr img {
|
||||
width: 100%; height: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.ftr-r {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.row .lbl {
|
||||
font-size: 11px; color: var(--text-light);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.row .val {
|
||||
font-size: 13px; color: var(--text);
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
}
|
||||
.row .val.big {
|
||||
font-size: 26px; font-weight: 700;
|
||||
color: var(--red);
|
||||
font-family: 'Noto Serif SC', serif;
|
||||
}
|
||||
.row .val.code {
|
||||
font-family: 'SF Mono', 'Courier New', monospace;
|
||||
font-size: 10px; letter-spacing: 0.06em;
|
||||
color: var(--text-mid);
|
||||
}
|
||||
.split {
|
||||
height: 1px;
|
||||
background: var(--line);
|
||||
margin: 4px 0;
|
||||
}
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status::before {
|
||||
content: '';
|
||||
width: 6px; height: 6px;
|
||||
background: #27AE60;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* ── Ticket stub number ── */
|
||||
.stub {
|
||||
margin: 0 32px;
|
||||
padding: 14px 0;
|
||||
border-top: 1px solid var(--line);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.stub span {
|
||||
font-family: 'SF Mono', 'Courier New', monospace;
|
||||
font-size: 10px; color: var(--text-light);
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* ── Print ── */
|
||||
@media print {
|
||||
@page { margin: 12mm; size: auto; }
|
||||
body { background: #FFF; padding: 0; }
|
||||
.ticket {
|
||||
box-shadow: none;
|
||||
border: 1px solid #D0CBC0;
|
||||
width: 370px;
|
||||
margin: 0 auto;
|
||||
animation: none;
|
||||
}
|
||||
.hdr { background: var(--red); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.hdr-title, .hdr-sub { color: #FFF; }
|
||||
.tear-circle { background: #FFF; }
|
||||
}
|
||||
|
||||
@media (max-width: 460px) {
|
||||
.ticket { width: 100%; }
|
||||
.main, .ftr, .stub { padding-left: 20px; padding-right: 20px; }
|
||||
.tear { margin: 0 20px; }
|
||||
.stub { margin: 0 20px; }
|
||||
.film-title { font-size: 32px; }
|
||||
.qr { width: 120px; height: 120px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="ticket">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="hdr">
|
||||
<span class="hdr-title">猫眼电影</span>
|
||||
<span class="hdr-sub">收藏纪念<br>COLLECTIBLE</span>
|
||||
</div>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
<div class="cinema-row">
|
||||
<svg class="cinema-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
<span class="cinema-name">万达影城(朝阳大悦城IMAX店)</span>
|
||||
</div>
|
||||
|
||||
<div class="film-title">流浪地球3</div>
|
||||
<div class="film-sub">
|
||||
<span>3D IMAX</span>
|
||||
<span>科幻 / 冒险</span>
|
||||
<span>片长148分钟</span>
|
||||
</div>
|
||||
|
||||
<div class="sess">
|
||||
<div class="sess-col">
|
||||
<span class="sess-label">影厅</span>
|
||||
<span class="sess-val hi">3号IMAX厅</span>
|
||||
</div>
|
||||
<div class="sess-col">
|
||||
<span class="sess-label">座位</span>
|
||||
<span class="sess-val">8排12座</span>
|
||||
</div>
|
||||
<div class="sess-col">
|
||||
<span class="sess-label">时间</span>
|
||||
<span class="sess-val">2026.06.18 19:30</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tear line -->
|
||||
<div class="tear">
|
||||
<span class="tear-circle l"></span>
|
||||
<span class="tear-circle r"></span>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="ftr">
|
||||
<div class="ftr-l">
|
||||
<div class="qr">
|
||||
<img src="mookiletter.png" alt="二维码">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ftr-r">
|
||||
<div class="row">
|
||||
<span class="lbl">票价</span>
|
||||
<span class="val big">¥49.90</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="lbl">服务费</span>
|
||||
<span class="val">¥5.00</span>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
<div class="row">
|
||||
<span class="lbl">出票时间</span>
|
||||
<span class="val">2026-06-18 10:32</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="lbl">淘票票</span>
|
||||
<span class="val status">已出票</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="lbl">取票码</span>
|
||||
<span class="val code">8602 3100 0618 7251</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stub -->
|
||||
<div class="stub">
|
||||
<span>TICKET · COLLECTIBLE</span>
|
||||
<span>NO. MY202606181930</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user