| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import 'package:flutter/material.dart';
- import '../../../core/l10n/app_localizations.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../core/utils/number_format.dart';
- import '../../../providers/futures_provider.dart';
- /// 仓位详情页
- class PositionDetailScreen extends StatelessWidget {
- const PositionDetailScreen({
- super.key,
- required this.position,
- required this.symbol,
- });
- final FuturesPosition position;
- final String symbol;
- String _baseCoin(String sym) {
- if (sym.contains('/')) return sym.split('/').first;
- return sym.toUpperCase().replaceFirst(RegExp(r'USDT$'), '');
- }
- String _rawNum(double v) {
- if (v == v.truncateToDouble()) return v.toInt().toString();
- return v.toString();
- }
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- final isLong = position.side == OrderSide.long;
- final pnlColor = position.unrealizedPnl >= 0 ? AppColors.rise : AppColors.fall;
- final realizedColor = position.realizedPnl >= 0 ? AppColors.rise : AppColors.fall;
- final coinSymbol = _baseCoin(position.symbol);
- final l10n = AppLocalizations.of(context)!;
- final scaffoldBg = isDark ? AppColors.darkBg : AppColors.lightBgSecondary;
- final sideColor = isLong ? AppColors.rise : AppColors.fall;
- return Scaffold(
- backgroundColor: scaffoldBg,
- appBar: AppBar(
- elevation: 0,
- centerTitle: true,
- backgroundColor: isDark ? AppColors.darkBgSecondary : AppColors.lightBg,
- title: Text(
- l10n.positionDetail,
- style: TextStyle(
- color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w700),
- ),
- ),
- body: SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // ── 标的行 ────────────────────────────────────────
- Container(
- color: isDark ? AppColors.darkBgSecondary : AppColors.lightBg,
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
- child: Row(
- children: [
- // 多/空 chip
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
- decoration: BoxDecoration(
- color: sideColor,
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- isLong ? l10n.openLong : l10n.openShort,
- style: const TextStyle(
- color: Colors.white, fontSize: 11, fontWeight: FontWeight.w700),
- ),
- ),
- const SizedBox(width: 6),
- Expanded(
- child: Row(
- children: [
- Flexible(
- child: Text(
- position.symbol,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- color: cs.onSurface,
- fontSize: 15,
- fontWeight: FontWeight.w700),
- ),
- ),
- const SizedBox(width: 6),
- _SmTag(
- text: position.marginMode,
- color: switch (position.marginMode) {
- '分仓' || '逐仓' => AppColors.rankPurple,
- _ => AppColors.tagBlue,
- },
- ),
- const SizedBox(width: 4),
- _LevTag(leverage: position.leverage.toInt()),
- ],
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 8),
- // ── 价格 ──────────────────────────────────────────
- _InfoCard(rows: [
- _InfoRowData(l10n.openAvgPrice, _rawNum(position.entryPrice)),
- _InfoRowData(l10n.markPrice, _rawNum(position.markPrice), isLast: true),
- ]),
- const SizedBox(height: 8),
- // ── 持仓数据 ──────────────────────────────────────
- _InfoCard(rows: [
- _InfoRowData('${l10n.positionSize}($coinSymbol)', _rawNum(position.size)),
- _InfoRowData('${l10n.positionValue}(USDT)',
- formatAmount(position.size * position.markPrice)),
- _InfoRowData(
- l10n.estimatedLiqPrice,
- position.liquidationPrice > 0 ? _rawNum(position.liquidationPrice) : '--',
- valueColor: position.liquidationPrice > 0 ? AppColors.fall : null,
- ),
- _InfoRowData('${l10n.marginLabel}(USDT)', formatAmount(position.margin),
- isLast: true),
- ]),
- const SizedBox(height: 8),
- // ── 盈亏 ──────────────────────────────────────────
- _InfoCard(rows: [
- _InfoRowData(l10n.profitRateLabel, '${formatAmount(position.roe)}%',
- valueColor: pnlColor),
- _InfoRowData(l10n.unrealizedPnl, formatAmount(position.unrealizedPnl),
- valueColor: pnlColor),
- _InfoRowData(l10n.realizedPnl, formatAmount(position.realizedPnl),
- valueColor: realizedColor),
- _InfoRowData(
- '${l10n.fee}(USDT)',
- position.commissionFee > 0 ? formatAmount(position.commissionFee) : '--',
- isLast: true,
- ),
- ]),
- const SizedBox(height: 16),
- ],
- ),
- ),
- );
- }
- }
- // ── 共用 widgets ───────────────────────────────────────────────
- class _SmTag extends StatelessWidget {
- const _SmTag({required this.text, this.color});
- final String text;
- final Color? color;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- final c = color;
- if (c != null) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: c.withAlpha(isDark ? 45 : 25),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(text,
- style: TextStyle(
- color: c, fontSize: 11, fontWeight: FontWeight.w600)),
- );
- }
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: cs.onSurface.withAlpha(12),
- border: Border.all(color: cs.outline.withAlpha(80)),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(text,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153),
- fontSize: 11,
- fontWeight: FontWeight.w600)),
- );
- }
- }
- class _LevTag extends StatelessWidget {
- const _LevTag({required this.leverage});
- final int leverage;
- @override
- Widget build(BuildContext context) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: AppColors.leverageGoldBg,
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text('${leverage}X',
- style: const TextStyle(
- color: AppColors.leverageGold,
- fontSize: 11,
- fontWeight: FontWeight.w700)),
- );
- }
- }
- class _InfoRowData {
- const _InfoRowData(this.label, this.value,
- {this.valueColor, this.isLast = false});
- final String label;
- final String value;
- final Color? valueColor;
- final bool isLast;
- }
- class _InfoCard extends StatelessWidget {
- const _InfoCard({required this.rows});
- final List<_InfoRowData> rows;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final isDark = Theme.of(context).brightness == Brightness.dark;
- return Container(
- color: isDark ? AppColors.darkBgSecondary : AppColors.lightBg,
- child: Column(
- children: rows.map((r) {
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
- child: Row(
- children: [
- Expanded(
- child: Text(r.label,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- color: cs.onSurface.withAlpha(153), fontSize: 13)),
- ),
- const SizedBox(width: 8),
- Text(r.value,
- textAlign: TextAlign.end,
- style: TextStyle(
- color: r.valueColor ?? cs.onSurface,
- fontSize: 13,
- fontWeight: FontWeight.w500)),
- ],
- ),
- ),
- if (!r.isLast)
- Divider(
- height: 1,
- thickness: 0.5,
- indent: 16,
- endIndent: 16,
- color: cs.outline.withAlpha(60)),
- ],
- );
- }).toList(),
- ),
- );
- }
- }
|