Initial commit: Monisuo - 虚拟货币模拟交易平台

功能模块:
- 用户注册/登录/KYC
- 资金账户/交易账户
- 实时行情/币种管理
- 即时交易/充提审核
- 管理后台

技术栈:
- 后端: SpringBoot 2.2.4 + MyBatis Plus
- 前端: uni-app x (Vue3 + UTS)
- 数据库: MySQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sion
2026-03-21 20:52:33 +08:00
commit 7694a34ade
108 changed files with 12563 additions and 0 deletions

448
app/pages/asset/asset.uvue Normal file
View File

@@ -0,0 +1,448 @@
<template>
<view class="asset-container">
<!-- 总资产卡片 -->
<view class="total-card">
<text class="total-label">总资产估值(USDT)</text>
<text class="total-value">${{ totalAssets }}</text>
</view>
<!-- 账户切换 -->
<view class="account-tabs">
<text :class="['tab', activeAccount === 'fund' ? 'active' : '']" @click="activeAccount = 'fund'">资金账户</text>
<text :class="['tab', activeAccount === 'trade' ? 'active' : '']" @click="activeAccount = 'trade'">交易账户</text>
</view>
<!-- 资金账户 -->
<view v-if="activeAccount === 'fund'" class="account-section">
<view class="balance-card">
<text class="balance-label">USDT余额</text>
<text class="balance-value">{{ fundBalance }}</text>
</view>
<view class="action-btns">
<button class="action-btn deposit" @click="showDeposit = true">
<text class="btn-text">充值</text>
</button>
<button class="action-btn withdraw" @click="showWithdraw = true">
<text class="btn-text">提现</text>
</button>
<button class="action-btn transfer" @click="showTransfer = true">
<text class="btn-text">划转</text>
</button>
</view>
</view>
<!-- 交易账户 -->
<view v-if="activeAccount === 'trade'" class="trade-section">
<view class="position-list">
<view class="position-item" v-for="(pos, index) in positions" :key="index">
<view class="pos-left">
<text class="pos-icon">{{ pos.icon }}</text>
<view class="pos-info">
<text class="pos-code">{{ pos.coinCode }}</text>
<text class="pos-name">{{ pos.coinName }}</text>
</view>
</view>
<view class="pos-right">
<text class="pos-quantity">{{ pos.quantity }}</text>
<text class="pos-value">≈ ${{ pos.value }}</text>
</view>
</view>
</view>
</view>
<!-- 充值弹窗 -->
<view v-if="showDeposit" class="modal-mask" @click="showDeposit = false">
<view class="modal-content" @click.stop="">
<text class="modal-title">充值</text>
<view class="modal-form">
<text class="form-label">充值金额(USDT)</text>
<input class="form-input" type="digit" v-model="depositAmount" placeholder="请输入金额" />
</view>
<view class="modal-btns">
<button class="modal-btn cancel" @click="showDeposit = false"><text class="btn-text">取消</text></button>
<button class="modal-btn confirm" @click="handleDeposit"><text class="btn-text">确认</text></button>
</view>
</view>
</view>
<!-- 提现弹窗 -->
<view v-if="showWithdraw" class="modal-mask" @click="showWithdraw = false">
<view class="modal-content" @click.stop="">
<text class="modal-title">提现</text>
<view class="modal-form">
<text class="form-label">提现金额(USDT)</text>
<input class="form-input" type="digit" v-model="withdrawAmount" placeholder="请输入金额" />
</view>
<view class="modal-btns">
<button class="modal-btn cancel" @click="showWithdraw = false"><text class="btn-text">取消</text></button>
<button class="modal-btn confirm" @click="handleWithdraw"><text class="btn-text">确认</text></button>
</view>
</view>
</view>
<!-- 划转弹窗 -->
<view v-if="showTransfer" class="modal-mask" @click="showTransfer = false">
<view class="modal-content" @click.stop="">
<text class="modal-title">资金划转</text>
<view class="transfer-direction">
<text :class="['dir-item', transferDir === 1 ? 'active' : '']" @click="transferDir = 1">资金→交易</text>
<text :class="['dir-item', transferDir === 2 ? 'active' : '']" @click="transferDir = 2">交易→资金</text>
</view>
<view class="modal-form">
<text class="form-label">划转金额(USDT)</text>
<input class="form-input" type="digit" v-model="transferAmount" placeholder="请输入金额" />
</view>
<view class="modal-btns">
<button class="modal-btn cancel" @click="showTransfer = false"><text class="btn-text">取消</text></button>
<button class="modal-btn confirm" @click="handleTransfer"><text class="btn-text">确认</text></button>
</view>
</view>
</view>
</view>
</template>
<script lang="uts">
import { getOverview, getTradeAccount, transfer } from '@/api/asset.uts'
import { deposit, withdraw } from '@/api/fund.uts'
type PositionItem = {
coinCode: string
coinName: string
quantity: string
value: string
icon: string
}
const totalAssets = ref('0.00')
const fundBalance = ref('0.00')
const positions = ref<PositionItem[]>([])
const activeAccount = ref('fund')
const showDeposit = ref(false)
const showWithdraw = ref(false)
const showTransfer = ref(false)
const depositAmount = ref('')
const withdrawAmount = ref('')
const transferAmount = ref('')
const transferDir = ref(1)
onShow(() => {
loadData()
})
func loadData () {
getOverview()
.then((res) => {
const data = res.data as UTSJSONObject
totalAssets.value = formatAmount(data['totalAssets'] as number)
fundBalance.value = formatAmount(data['fundBalance'] as number)
})
.catch(() => {})
getTradeAccount()
.then((res) => {
const data = res.data as UTSJSONObject
const list = data['positions'] as Array<UTSJSONObject>
const items: PositionItem[] = []
for (let i = 0; i < list.length; i++) {
const item = list[i]
items.push({
coinCode: item['coinCode'] as string,
coinName: item['coinName'] as string,
quantity: (item['quantity'] as number).toFixed(4),
value: formatAmount(item['value'] as number),
icon: getCoinIcon(item['coinCode'] as string)
})
}
positions.value = items
})
.catch(() => {})
}
func handleDeposit () {
if (depositAmount.value === '' || parseFloat(depositAmount.value) <= 0) {
uni.showToast({ title: '请输入有效金额', icon: 'none' })
return
}
deposit(depositAmount.value, null)
.then(() => {
uni.showToast({ title: '申请成功,等待审批', icon: 'success' })
showDeposit.value = false
depositAmount.value = ''
loadData()
})
.catch(() => {})
}
func handleWithdraw () {
if (withdrawAmount.value === '' || parseFloat(withdrawAmount.value) <= 0) {
uni.showToast({ title: '请输入有效金额', icon: 'none' })
return
}
withdraw(withdrawAmount.value, null)
.then(() => {
uni.showToast({ title: '申请成功,等待审批', icon: 'success' })
showWithdraw.value = false
withdrawAmount.value = ''
loadData()
})
.catch(() => {})
}
func handleTransfer () {
if (transferAmount.value === '' || parseFloat(transferAmount.value) <= 0) {
uni.showToast({ title: '请输入有效金额', icon: 'none' })
return
}
transfer(transferDir.value, transferAmount.value)
.then(() => {
uni.showToast({ title: '划转成功', icon: 'success' })
showTransfer.value = false
transferAmount.value = ''
loadData()
})
.catch(() => {})
}
func formatAmount (value: number): string {
return value.toFixed(2)
}
func getCoinIcon (code: string): string {
const icons: Map<string, string> = new Map()
icons.set('BTC', '₿')
icons.set('ETH', 'Ξ')
icons.set('SOL', '◎')
icons.set('USDT', '₮')
return icons.get(code) || '●'
}
</script>
<style lang="scss">
.asset-container {
min-height: 100vh;
background: #1A1A2E;
padding: 24rpx;
padding-bottom: 120rpx;
}
.total-card {
background: linear-gradient(135deg, #00D4AA 0%, #00B894 100%);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.total-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8rpx;
}
.total-value {
font-size: 48rpx;
font-weight: bold;
color: #fff;
}
.account-tabs {
display: flex;
flex-direction: row;
background: #16213E;
border-radius: 16rpx;
padding: 8rpx;
margin-bottom: 24rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
border-radius: 12rpx;
}
.tab.active {
background: rgba(0, 212, 170, 0.2);
color: #00D4AA;
}
.balance-card {
background: #16213E;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.balance-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 8rpx;
}
.balance-value {
font-size: 40rpx;
font-weight: bold;
color: #00D4AA;
}
.action-btns {
display: flex;
flex-direction: row;
gap: 16rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn.deposit { background: #00C853; }
.action-btn.withdraw { background: #FF5252; }
.action-btn.transfer { background: #00D4AA; }
.btn-text {
font-size: 28rpx;
font-weight: bold;
color: #fff;
}
.position-list {
background: #16213E;
border-radius: 20rpx;
padding: 24rpx;
}
.position-item {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
}
.position-item:last-child { border-bottom: none; }
.pos-left {
display: flex;
flex-direction: row;
align-items: center;
}
.pos-icon {
font-size: 32rpx;
width: 48rpx;
height: 48rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
}
.pos-info { display: flex; flex-direction: column; }
.pos-code { font-size: 28rpx; font-weight: bold; color: #fff; }
.pos-name { font-size: 22rpx; color: rgba(255, 255, 255, 0.5); }
.pos-right { display: flex; flex-direction: column; align-items: flex-end; }
.pos-quantity { font-size: 28rpx; color: #fff; }
.pos-value { font-size: 22rpx; color: rgba(255, 255, 255, 0.5); }
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.modal-content {
width: 600rpx;
background: #16213E;
border-radius: 24rpx;
padding: 32rpx;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
text-align: center;
margin-bottom: 32rpx;
}
.modal-form {
margin-bottom: 32rpx;
}
.form-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 12rpx;
}
.form-input {
width: 100%;
height: 80rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 32rpx;
color: #fff;
box-sizing: border-box;
}
.transfer-direction {
display: flex;
flex-direction: row;
margin-bottom: 24rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 12rpx;
padding: 8rpx;
}
.dir-item {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
border-radius: 8rpx;
}
.dir-item.active {
background: #00D4AA;
color: #fff;
}
.modal-btns {
display: flex;
flex-direction: row;
gap: 24rpx;
}
.modal-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.modal-btn.cancel { background: rgba(255, 255, 255, 0.1); }
.modal-btn.confirm { background: #00D4AA; }
</style>