111
This commit is contained in:
@@ -62,13 +62,11 @@ class MyApp extends StatelessWidget {
|
|||||||
providers: _buildProviders(),
|
providers: _buildProviders(),
|
||||||
child: Consumer<ThemeProvider>(
|
child: Consumer<ThemeProvider>(
|
||||||
builder: (context, themeProvider, _) {
|
builder: (context, themeProvider, _) {
|
||||||
return AuthNavigator(
|
return ShadApp.custom(
|
||||||
child: ShadApp.custom(
|
themeMode: themeProvider.themeMode,
|
||||||
themeMode: themeProvider.themeMode,
|
theme: createLightShadTheme(),
|
||||||
theme: createLightShadTheme(),
|
darkTheme: createDarkShadTheme(),
|
||||||
darkTheme: createDarkShadTheme(),
|
appBuilder: _buildMaterialApp,
|
||||||
appBuilder: _buildMaterialApp,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -134,55 +132,3 @@ class MyApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 认证路由守卫 - 监听认证状态并自动导航
|
|
||||||
class AuthNavigator extends StatefulWidget {
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
const AuthNavigator({super.key, required this.child});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AuthNavigator> createState() => _AuthNavigatorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AuthNavigatorState extends State<AuthNavigator> {
|
|
||||||
bool? _wasLoggedIn;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
final isLoggedIn = context.watch<AuthProvider>().isLoggedIn;
|
|
||||||
|
|
||||||
if (_wasLoggedIn == null) {
|
|
||||||
_wasLoggedIn = isLoggedIn;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_wasLoggedIn != isLoggedIn) {
|
|
||||||
_wasLoggedIn = isLoggedIn;
|
|
||||||
_navigateToAuthPage(isLoggedIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _navigateToAuthPage(bool isLoggedIn) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
// 退出登录时重置其他 Provider 的状态
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
context.read<AssetProvider>().resetLoadState();
|
|
||||||
context.read<MarketProvider>().resetLoadState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => isLoggedIn ? const MainPage() : const LoginPage(),
|
|
||||||
),
|
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => widget.child;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ class _AssetCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 代币列表
|
/// 资产列表
|
||||||
class _TokenList extends StatelessWidget {
|
class _TokenList extends StatelessWidget {
|
||||||
final List holdings;
|
final List holdings;
|
||||||
|
|
||||||
@@ -399,11 +399,22 @@ class _TokenList extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
// 对持仓进行排序:USDT 放在最上面
|
||||||
|
final sortedHoldings = List.from(holdings);
|
||||||
|
sortedHoldings.sort((a, b) {
|
||||||
|
final codeA = (a.coinCode ?? a['coinCode'] ?? '').toString().toUpperCase();
|
||||||
|
final codeB = (b.coinCode ?? b['coinCode'] ?? '').toString().toUpperCase();
|
||||||
|
// USDT 排在最前面
|
||||||
|
if (codeA == 'USDT') return -1;
|
||||||
|
if (codeB == 'USDT') return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'代币列表',
|
'资产列表',
|
||||||
style: GoogleFonts.spaceGrotesk(
|
style: GoogleFonts.spaceGrotesk(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -411,18 +422,18 @@ class _TokenList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: AppSpacing.md),
|
SizedBox(height: AppSpacing.md),
|
||||||
if (holdings.isEmpty)
|
if (sortedHoldings.isEmpty)
|
||||||
_EmptyState(icon: LucideIcons.wallet, message: '暂无持仓')
|
_EmptyState(icon: LucideIcons.wallet, message: '暂无持仓')
|
||||||
else
|
else
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: holdings.length,
|
itemCount: sortedHoldings.length,
|
||||||
separatorBuilder: (_, __) => Divider(
|
separatorBuilder: (_, __) => Divider(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: colorScheme.outlineVariant.withOpacity(0.2),
|
color: colorScheme.outlineVariant.withOpacity(0.2),
|
||||||
),
|
),
|
||||||
itemBuilder: (context, index) => _TokenItem(holding: holdings[index]),
|
itemBuilder: (context, index) => _TokenItem(holding: sortedHoldings[index]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -176,10 +176,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToMainPage() {
|
void _navigateToMainPage() {
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
// 不使用 Navigator,让 main.dart 中的 Consumer<AuthProvider> 自动处理页面切换
|
||||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
// 登录成功后,auth.isLoggedIn 会变为 true,Consumer 会自动显示 MainPage
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToRegister() {
|
void _navigateToRegister() {
|
||||||
|
|||||||
@@ -201,6 +201,11 @@ public class AdminController {
|
|||||||
String code = (String) params.get("code");
|
String code = (String) params.get("code");
|
||||||
BigDecimal price = new BigDecimal(params.get("price").toString());
|
BigDecimal price = new BigDecimal(params.get("price").toString());
|
||||||
|
|
||||||
|
// USDT价格固定为1,不允许修改
|
||||||
|
if ("USDT".equalsIgnoreCase(code)) {
|
||||||
|
return Result.fail("USDT价格固定为1,不可修改");
|
||||||
|
}
|
||||||
|
|
||||||
Coin coin = coinService.getCoinByCode(code);
|
Coin coin = coinService.getCoinByCode(code);
|
||||||
if (coin == null) {
|
if (coin == null) {
|
||||||
return Result.fail("币种不存在");
|
return Result.fail("币种不存在");
|
||||||
|
|||||||
@@ -153,16 +153,35 @@ public class AssetService {
|
|||||||
// 获取交易账户USDT持仓
|
// 获取交易账户USDT持仓
|
||||||
AccountTrade tradeUsdt = getOrCreateTradeAccount(userId, "USDT");
|
AccountTrade tradeUsdt = getOrCreateTradeAccount(userId, "USDT");
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
if (direction == 1) {
|
if (direction == 1) {
|
||||||
// 资金账户 -> 交易账户
|
// 资金账户 -> 交易账户
|
||||||
if (fund.getBalance().compareTo(amount) < 0) {
|
if (fund.getBalance().compareTo(amount) < 0) {
|
||||||
throw new RuntimeException("资金账户余额不足");
|
throw new RuntimeException("资金账户余额不足");
|
||||||
}
|
}
|
||||||
|
BigDecimal fundBalanceBefore = fund.getBalance();
|
||||||
|
BigDecimal tradeBalanceBefore = tradeUsdt.getQuantity();
|
||||||
|
|
||||||
fund.setBalance(fund.getBalance().subtract(amount));
|
fund.setBalance(fund.getBalance().subtract(amount));
|
||||||
tradeUsdt.setQuantity(tradeUsdt.getQuantity().add(amount));
|
tradeUsdt.setQuantity(tradeUsdt.getQuantity().add(amount));
|
||||||
|
|
||||||
|
// 使用 LambdaUpdateWrapper 显式更新资金账户
|
||||||
|
LambdaUpdateWrapper<AccountFund> fundUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
fundUpdateWrapper.eq(AccountFund::getId, fund.getId())
|
||||||
|
.set(AccountFund::getBalance, fund.getBalance())
|
||||||
|
.set(AccountFund::getUpdateTime, now);
|
||||||
|
accountFundMapper.update(null, fundUpdateWrapper);
|
||||||
|
|
||||||
|
// 使用 LambdaUpdateWrapper 显式更新交易账户
|
||||||
|
LambdaUpdateWrapper<AccountTrade> tradeUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
tradeUpdateWrapper.eq(AccountTrade::getId, tradeUsdt.getId())
|
||||||
|
.set(AccountTrade::getQuantity, tradeUsdt.getQuantity())
|
||||||
|
.set(AccountTrade::getUpdateTime, now);
|
||||||
|
accountTradeMapper.update(null, tradeUpdateWrapper);
|
||||||
|
|
||||||
// 记录流水
|
// 记录流水
|
||||||
createFlow(userId, 4, amount.negate(), fund.getBalance().add(amount),
|
createFlow(userId, 4, amount.negate(), fundBalanceBefore,
|
||||||
fund.getBalance(), "USDT", null, "划转至交易账户");
|
fund.getBalance(), "USDT", null, "划转至交易账户");
|
||||||
|
|
||||||
} else if (direction == 2) {
|
} else if (direction == 2) {
|
||||||
@@ -170,22 +189,37 @@ public class AssetService {
|
|||||||
if (tradeUsdt.getQuantity().compareTo(amount) < 0) {
|
if (tradeUsdt.getQuantity().compareTo(amount) < 0) {
|
||||||
throw new RuntimeException("交易账户USDT余额不足");
|
throw new RuntimeException("交易账户USDT余额不足");
|
||||||
}
|
}
|
||||||
|
BigDecimal fundBalanceBefore = fund.getBalance();
|
||||||
|
BigDecimal tradeBalanceBefore = tradeUsdt.getQuantity();
|
||||||
|
|
||||||
tradeUsdt.setQuantity(tradeUsdt.getQuantity().subtract(amount));
|
tradeUsdt.setQuantity(tradeUsdt.getQuantity().subtract(amount));
|
||||||
fund.setBalance(fund.getBalance().add(amount));
|
fund.setBalance(fund.getBalance().add(amount));
|
||||||
|
|
||||||
|
// 使用 LambdaUpdateWrapper 显式更新资金账户
|
||||||
|
LambdaUpdateWrapper<AccountFund> fundUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
fundUpdateWrapper.eq(AccountFund::getId, fund.getId())
|
||||||
|
.set(AccountFund::getBalance, fund.getBalance())
|
||||||
|
.set(AccountFund::getUpdateTime, now);
|
||||||
|
accountFundMapper.update(null, fundUpdateWrapper);
|
||||||
|
|
||||||
|
// 使用 LambdaUpdateWrapper 显式更新交易账户
|
||||||
|
LambdaUpdateWrapper<AccountTrade> tradeUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
tradeUpdateWrapper.eq(AccountTrade::getId, tradeUsdt.getId())
|
||||||
|
.set(AccountTrade::getQuantity, tradeUsdt.getQuantity())
|
||||||
|
.set(AccountTrade::getUpdateTime, now);
|
||||||
|
accountTradeMapper.update(null, tradeUpdateWrapper);
|
||||||
|
|
||||||
// 记录流水
|
// 记录流水
|
||||||
createFlow(userId, 3, amount, fund.getBalance().subtract(amount),
|
createFlow(userId, 3, amount, fundBalanceBefore,
|
||||||
fund.getBalance(), "USDT", null, "划转至资金账户");
|
fund.getBalance(), "USDT", null, "划转至资金账户");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("无效的划转方向");
|
throw new RuntimeException("无效的划转方向");
|
||||||
}
|
}
|
||||||
|
|
||||||
fund.setUpdateTime(LocalDateTime.now());
|
System.out.println("[划转成功] 用户ID=" + userId + ", 方向=" + (direction == 1 ? "资金→交易" : "交易→资金") +
|
||||||
accountFundMapper.updateById(fund);
|
", 金额=" + amount + " USDT, 资金账户余额=" + fund.getBalance() +
|
||||||
|
", 交易账户USDT=" + tradeUsdt.getQuantity());
|
||||||
tradeUsdt.setUpdateTime(LocalDateTime.now());
|
|
||||||
accountTradeMapper.updateById(tradeUsdt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -36,8 +36,13 @@ public class CoinService extends ServiceImpl<CoinMapper, Coin> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新币种价格
|
* 更新币种价格
|
||||||
|
* 注意:USDT价格固定为1,不允许修改
|
||||||
*/
|
*/
|
||||||
public void updatePrice(String code, BigDecimal price) {
|
public void updatePrice(String code, BigDecimal price) {
|
||||||
|
// USDT价格固定为1,不允许修改
|
||||||
|
if ("USDT".equalsIgnoreCase(code)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Coin coin = getCoinByCode(code);
|
Coin coin = getCoinByCode(code);
|
||||||
if (coin != null) {
|
if (coin != null) {
|
||||||
coin.setPrice(price);
|
coin.setPrice(price);
|
||||||
|
|||||||
Reference in New Issue
Block a user