Files
sbnews/uniapp/src/pages/crypto/crypto.vue
T

377 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="crypto-page">
<!-- 状态栏占位 -->
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<!-- 顶部导航 -->
<view class="navbar">
<text class="navbar__title">加密行情</text>
<AiAssistantEntry size="compact" />
</view>
<!-- 排序 Tab + 搜索 -->
<view class="market-tabs">
<view v-for="(tab, i) in marketTabs" :key="i" class="market-tab" :class="{ active: currentTab === i }"
@tap="switchTab(i)">
<text>{{ tab.label }}</text>
</view>
<view class="market-tabs__search">
<u-icon name="search" size="24" color="#999" />
<input class="market-tabs__input" v-model="searchKey" placeholder="搜索" placeholder-style="color:#ccc"
confirm-type="search" />
<u-icon v-if="searchKey" name="close-circle-fill" size="28" color="#ccc" @click="searchKey = ''" />
</view>
</view>
<!-- 列表头 -->
<view class="list-header">
<text class="list-header__col">名称</text>
<text class="list-header__col">最新价</text>
<text class="list-header__col">24h涨跌</text>
</view>
<!-- 币种列表 -->
<scroll-view scroll-y class="coin-list" :style="{ height: coinListHeight }">
<view v-for="coin in displayCoins" :key="coin.symbol" class="coin-row" @tap="goCoinDetail(coin)">
<!-- Logo + 名称 -->
<view class="coin-row__left">
<view class="coin-row__logo" :style="{ background: coin.color + '18' }">
<text class="coin-row__logo-text" :style="{ color: coin.color }">{{ coin.icon }}</text>
</view>
<view class="coin-row__name">
<text class="coin-row__symbol">{{ coin.symbol }}</text>
<text class="coin-row__subtitle">{{ coin.name }}</text>
</view>
</view>
<!-- 价格 + 市值 -->
<view class="coin-row__price">
<text class="coin-row__price-val">${{ coin.price }}</text>
<text class="coin-row__mcap">市值{{ coin.mcap }}</text>
</view>
<!-- 涨跌 Badge -->
<view class="coin-row__badge" :class="coin.change >= 0 ? 'up' : 'down'">
<text class="coin-row__badge-arrow">{{ coin.change >= 0 ? '▲' : '▼' }}</text>
<text class="coin-row__badge-text">{{ Math.abs(coin.change).toFixed(2) }}%</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onUnmounted } from 'vue'
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
import AiAssistantEntry from '@/components/ai-assistant-entry/ai-assistant-entry.vue'
import { getBinanceTickers, loadCoinConfig, type CoinTicker } from '@/api/crypto'
const statusBarHeight = ref(0)
const windowHeight = ref(667)
const coinListHeight = computed(() => {
const fixedHeight = statusBarHeight.value + uni.upx2px(232)
return `${windowHeight.value - fixedHeight}px`
})
const allCoins = ref<CoinTicker[]>([])
const searchKey = ref('')
const marketTabs = ref([
{ label: '热门', key: 'hot' },
{ label: '涨幅榜', key: 'gainers' },
{ label: '跌幅榜', key: 'losers' },
{ label: '成交额', key: 'volume' }
])
const currentTab = ref(0)
const displayCoins = computed(() => {
let list = [...allCoins.value]
const kw = searchKey.value.trim().toLowerCase()
if (kw) {
list = list.filter(c => c.symbol.toLowerCase().includes(kw) || c.name.toLowerCase().includes(kw))
}
if (currentTab.value === 1) return list.sort((a, b) => b.change - a.change)
if (currentTab.value === 2) return list.sort((a, b) => a.change - b.change)
if (currentTab.value === 3) return list.sort((a, b) => b.priceRaw - a.priceRaw)
return list
})
const fetchTickers = async () => {
console.log('[CryptoList] fetchTickers start')
try {
allCoins.value = await getBinanceTickers()
console.log('[CryptoList] fetchTickers ok count=>', allCoins.value.length)
} catch (e) {
console.error('[CryptoList] fetchTickers error=>', e)
uni.showToast({ title: '行情加载失败', icon: 'none' })
}
}
const switchTab = (i: number) => { currentTab.value = i }
const goCoinDetail = (coin: any) => {
uni.navigateTo({ url: `/pages/crypto/crypto_detail?symbol=${coin.symbol}` })
}
let refreshTimer: ReturnType<typeof setInterval> | null = null
const startAutoRefresh = () => {
stopAutoRefresh()
refreshTimer = setInterval(fetchTickers, 30000)
}
const stopAutoRefresh = () => {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
}
onLoad(() => {
uni.getSystemInfo({
success: (res) => {
windowHeight.value = res.windowHeight || 667
// #ifdef APP-PLUS || MP
statusBarHeight.value = res.statusBarHeight || 44
// #endif
// #ifdef H5
statusBarHeight.value = 0
// #endif
}
})
loadCoinConfig().then(fetchTickers)
})
onShow(() => {
startAutoRefresh()
})
onHide(() => { stopAutoRefresh() })
onUnmounted(() => { stopAutoRefresh() })
</script>
<style lang="scss" scoped>
// Design tokens from Figma
$bg: #ffffff;
$text-primary: #000000;
$text-secondary: #999999;
$border: rgba(0, 0, 0, 0.06);
$up: #ff2c58;
$down: #00c482;
$accent: #333333;
.crypto-page {
background: $bg;
min-height: 100vh;
color: $text-primary;
}
.status-bar {
background: $bg;
}
/* 导航栏 */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
padding: 0 32rpx;
height: 88rpx;
background: $bg;
&__title {
font-size: 36rpx;
font-weight: 700;
color: $text-primary;
}
}
/* 排序Tab */
.market-tabs {
display: flex;
padding: 0 32rpx;
gap: 8rpx;
height: 72rpx;
align-items: center;
&__search {
margin-left: auto;
display: flex;
align-items: center;
gap: 8rpx;
background: #f5f5f5;
border-radius: 28rpx;
padding: 0 16rpx;
height: 52rpx;
flex: 1;
max-width: 200px;
}
&__input {
font-size: 24rpx;
width: 0;
flex: 1;
height: 52rpx;
line-height: 52rpx;
color: $text-primary;
}
}
.market-tab {
padding: 10rpx 18rpx;
font-size: 24rpx;
white-space: nowrap;
flex-shrink: 0;
color: $text-secondary;
border-radius: 32rpx;
transition: all 0.2s;
&.active {
color: #fff;
background: $accent;
font-weight: 600;
}
}
/* 列表头 — Figma: 402x41, bg=#F5F5F5, text=#666 */
.list-header {
display: flex;
align-items: center;
padding: 0 32rpx;
height: 72rpx;
background: #f5f5f5;
&__col {
font-size: 22rpx;
color: #666666;
&:first-child {
flex: 1;
text-align: left;
padding-left: 60rpx;
}
&:nth-child(2) {
width: 160rpx;
text-align: right;
margin-right: 24rpx;
}
&:last-child {
width: 168rpx;
text-align: center;
}
}
}
/* 币种列表 */
.coin-list {
background: $bg;
}
/* 行情行 — 匹配 Figma: 370x65 → 740x130rpx */
.coin-row {
display: flex;
align-items: center;
padding: 0 32rpx;
height: 130rpx;
border-bottom: 1rpx solid $border;
&__left {
flex: 1;
display: flex;
align-items: center;
gap: 20rpx;
min-width: 0;
}
&__logo {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
&__logo-text {
font-size: 28rpx;
font-weight: 700;
}
&__name {
display: flex;
flex-direction: column;
gap: 4rpx;
min-width: 0;
}
&__symbol {
font-size: 30rpx;
font-weight: 600;
color: $text-primary;
}
&__subtitle {
font-size: 24rpx;
color: $text-secondary;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__price {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4rpx;
margin-right: 24rpx;
}
&__price-val {
font-size: 30rpx;
font-weight: 600;
color: $text-primary;
}
&__mcap {
font-size: 24rpx;
color: $text-secondary;
}
/* 涨跌Badge — Figma: 84x32 radius=12 */
&__badge {
width: 168rpx;
height: 64rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 6rpx;
flex-shrink: 0;
&.up {
background: $up;
}
&.down {
background: $down;
}
}
&__badge-arrow {
font-size: 18rpx;
color: #ffffff;
}
&__badge-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
}
</style>