| | |
| | | <div class="header"> |
| | | <div class="header-left"> |
| | | <img src="@/assets/image/login_logo.png" alt="Wealthinfra" class="logo" /> |
| | | <span class="logo-text">Wealthinfra</span> |
| | | <span class="logo-text">Bitget</span> |
| | | </div> |
| | | <div class="header-right"> |
| | | <div class="lang-selector" @click="$router.push('/language')"> |
| | |
| | | @click="goToOptions(pair.symbol, pair.type)"> |
| | | <div class="currency-info"> |
| | | <div class="currency-pair-row"> |
| | | <template v-if="activeTab === 'forex' && getPairIconUrl(pair)"> |
| | | <div class="currency-card-icon-wrap"> |
| | | <img :src="getPairIconUrl(pair)" alt="" |
| | | class="currency-card-icon currency-card-icon--large" /> |
| | | <img v-if="getPairIconUrlSm(pair)" :src="getPairIconUrlSm(pair)" alt="" |
| | | class="currency-card-icon currency-card-icon--sm" /> |
| | | </div> |
| | | </template> |
| | | <img v-else-if="getPairIconUrl(pair)" :src="getPairIconUrl(pair)" alt="" |
| | | <img v-if="getPairIconUrl(pair)" :src="getPairIconUrl(pair)" alt="" |
| | | class="currency-card-icon" /> |
| | | <div class="currency-pair">{{ pair.symboltxt.toUpperCase() }}</div> |
| | | </div> |
| | |
| | | <!-- Trading Instruments --> |
| | | <div class="trading-section"> |
| | | <div class="trading-tabs"> |
| | | <div class="tab-item" :class="{ active: activeTab === 'forex' }" @click="activeTab = 'forex'"> |
| | | {{ $t('外汇') }} |
| | | </div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'crypto' }" @click="activeTab = 'crypto'"> |
| | | {{ $t('加密货币') }} |
| | | </div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'stock' }" @click="activeTab = 'stock'"> |
| | | {{ $t('股票') }} |
| | | </div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'etf' }" @click="activeTab = 'etf'"> |
| | | ETF |
| | | <div class="tab-item" v-for="tab in marketTabs" :key="tab.value" |
| | | :class="{ active: activeTab === tab.value }" @click="switchTab(tab.value)"> |
| | | {{ $t(tab.label) }} |
| | | </div> |
| | | </div> |
| | | <div class="trading-pairs"> |
| | |
| | | @click="goToOptions(pair.symbol, pair.type)"> |
| | | <div class="pair-header"> |
| | | <div class="pair-symbol"> |
| | | <template v-if="activeTab === 'forex' && getPairIconUrl(pair)"> |
| | | <div class="pair-symbol-icon-wrap"> |
| | | <img :src="getPairIconUrl(pair)" alt="" |
| | | class="pair-symbol-icon pair-symbol-icon--large" /> |
| | | <img v-if="getPairIconUrlSm(pair)" :src="getPairIconUrlSm(pair)" alt="" |
| | | class="pair-symbol-icon pair-symbol-icon--sm" /> |
| | | </div> |
| | | </template> |
| | | <img v-else-if="getPairIconUrl(pair)" :src="getPairIconUrl(pair)" alt="" |
| | | <img v-if="getPairIconUrl(pair)" :src="getPairIconUrl(pair)" alt="" |
| | | class="pair-symbol-icon" /> |
| | | {{ pair.symboltxt.toUpperCase() }} |
| | | {{ (pair.symboltxt || pair.symbol || '').toUpperCase() }} |
| | | </div> |
| | | <div class="pair-change" :class="pair.change >= 0 ? 'up' : 'down'"> |
| | | {{ pair.change >= 0 ? '+' : '' }}{{ pair.change.toFixed(4) }}% |
| | |
| | | <li>{{ $t('home.teamIntro') }}</li> |
| | | <li>{{ $t('帮助中心') }}</li> |
| | | <li>{{ $t('home.emailSupport') }}</li> |
| | | <li>support@wealthinfra.com</li> |
| | | <li>support@1m.com</li> |
| | | </ul> |
| | | </div> |
| | | <div class="info-section"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, watch } from 'vue' |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { useRouter } from 'vue-router' |
| | | import { IMG_PATH } from '@/config' |
| | |
| | | import { _getRealtimeByType } from '@/service/quotes.api' |
| | | import MiniKlineChart from '@/components/MiniKlineChart/index.vue' |
| | | |
| | | // 外汇货币代码 -> 国旗图国家/地区代码(用于 flagcdn.com) |
| | | const CURRENCY_TO_FLAG = { |
| | | eur: 'eu', usd: 'us', gbp: 'gb', jpy: 'jp', chf: 'ch', aud: 'au', cad: 'ca', nzd: 'nz', |
| | | cny: 'cn', cnh: 'cn', hkd: 'hk', sgd: 'sg', nok: 'no', sek: 'se', dkk: 'dk', mxn: 'mx', |
| | | zar: 'za', try: 'tr', pln: 'pl', inr: 'in', krw: 'kr', thb: 'th', myr: 'my', idr: 'id', |
| | | php: 'ph', brl: 'br', rub: 'ru', czk: 'cz', huf: 'hu', ron: 'ro', bgn: 'bg', hrk: 'hr' |
| | | } |
| | | const FLAG_CDN = 'https://flagcdn.com/w40' |
| | | import { _getUsHeadNews } from '@/service/user.api' |
| | | // import newsPlaceholder from '@/assets/image/news-placeholder.png' |
| | | |
| | |
| | | if (!symbol) return |
| | | router.push({ |
| | | path: '/trade/options', |
| | | query: { symbol, activeTab: type || 'cryptos' } |
| | | query: { symbol, activeTab: type || activeTab.value } |
| | | }) |
| | | } |
| | | |
| | |
| | | return langMap[locale.value] || '简体' |
| | | }) |
| | | |
| | | const activeTab = ref('forex') |
| | | |
| | | const tradingPairs = ref([]) |
| | | const activeTab = ref('cryptos') |
| | | const marketTabs = [ |
| | | { label: '加密货币', value: 'cryptos' }, |
| | | { label: '股票', value: 'US-stocks' }, |
| | | { label: 'ETF', value: 'indices' }, |
| | | { label: '外汇', value: 'forex' }, |
| | | ] |
| | | // 新闻列表,用于横向滚动轮播(与 news/index.vue 同源:_getUsHeadNews) |
| | | const newsList = ref([]) |
| | | |
| | | // Market Overview 使用 Trading Instruments 的前3条数据(保留完整 pair 用于展示图标) |
| | | const currencyPairs = computed(() => tradingPairs.value.slice(0, 3)) |
| | | |
| | | // 从外汇对取基础货币代码,如 EUR/USD -> eur,EURUSD -> eur(无斜杠取前3位) |
| | | function getForexBaseCurrency(symbol) { |
| | | if (!symbol || typeof symbol !== 'string') return '' |
| | | const s = symbol.trim() |
| | | if (s.includes('/')) return s.split('/')[0].trim().toLowerCase() |
| | | return s.slice(0, 3).toLowerCase() |
| | | } |
| | | |
| | | // 从外汇对取计价货币代码,如 EUR/USD -> usd(右下角小图用) |
| | | function getForexQuoteCurrency(symbol) { |
| | | if (!symbol || typeof symbol !== 'string') return '' |
| | | const s = symbol.trim() |
| | | if (s.includes('/')) return s.split('/')[1]?.trim().toLowerCase() || '' |
| | | return s.length > 3 ? s.slice(3, 6).toLowerCase() : '' |
| | | } |
| | | |
| | | // 列表项图标地址:外汇用国旗,加密货币用 symbol 图;股票、ETF 不展示图标 |
| | | function getPairIconUrl(pair) { |
| | | if (!pair) return '' |
| | | if (activeTab.value === 'stock' || activeTab.value === 'etf') return '' |
| | | if (activeTab.value === 'forex') { |
| | | const code = CURRENCY_TO_FLAG[getForexBaseCurrency(pair.symbol)] |
| | | return code ? `${FLAG_CDN}/${code}.png` : '' |
| | | } |
| | | if (activeTab.value === 'US-stocks' || activeTab.value === 'indices') return '' |
| | | return pair.iconImg ? `${IMG_PATH}/symbol/${pair.iconImg}.png` : '' |
| | | } |
| | | |
| | | // 小图用名字后面3位(计价货币)的国旗,仅外汇有效 |
| | | function getPairIconUrlSm(pair) { |
| | | if (!pair || activeTab.value !== 'forex') return '' |
| | | const quote = getForexQuoteCurrency(pair.symbol) |
| | | if (!quote) return '' |
| | | const code = CURRENCY_TO_FLAG[quote] |
| | | return code ? `${FLAG_CDN}/${code}.png` : '' |
| | | function switchTab(tab) { |
| | | if (activeTab.value === tab) return |
| | | activeTab.value = tab |
| | | fetchTradingData() |
| | | } |
| | | |
| | | // 获取交易数据 |
| | | const fetchTradingData = async () => { |
| | | let type = '' |
| | | let category = null |
| | | |
| | | switch (activeTab.value) { |
| | | case 'crypto': |
| | | type = 'cryptos' |
| | | break |
| | | case 'etf': |
| | | type = 'indices' |
| | | break |
| | | case 'stock': |
| | | type = 'US-stocks' |
| | | break |
| | | case 'forex': |
| | | type = 'forex' |
| | | category = 'forex' |
| | | break |
| | | default: |
| | | type = 'forex' |
| | | category = 'forex' |
| | | } |
| | | const type = activeTab.value |
| | | |
| | | try { |
| | | const params = { |
| | | type: type, |
| | | pageNo: 1 |
| | | } |
| | | if (category) { |
| | | params.category = category |
| | | } |
| | | |
| | | const data = await _getRealtimeByType(params) |
| | | |
| | | if (data && Array.isArray(data)) { |
| | | // 外汇 tab 只展示指定 symbol:EURUSD GBPUSD AUDUSD XAUUSD NZDUSD |
| | | const forexAllowedSymbols = ['EURUSD', 'GBPUSD', 'AUDUSD', 'XAUUSD', 'NZDUSD'] |
| | | let list = data |
| | | if (activeTab.value === 'forex') { |
| | | list = data.filter(item => { |
| | | const raw = (item.symbol || item.enName || '').toString().trim() |
| | | const normalized = raw.replace(/\//g, '').toUpperCase() |
| | | return forexAllowedSymbols.includes(normalized) |
| | | }) |
| | | } |
| | | const list = data |
| | | // 只取前5条数据,并转换为需要的格式 |
| | | tradingPairs.value = list.slice(0, 5).map(item => { |
| | | const basePrice = parseFloat(item.close || item.lastPrice || 0) |
| | |
| | | const spread = basePrice * 0.0001 // 很小的价差 |
| | | const sellPrice = (basePrice - spread).toFixed(4) |
| | | const buyPrice = (basePrice + spread).toFixed(4) |
| | | const symboltxt = item.enName |
| | | const symboltxt = item.enName || item.name || item.symbol |
| | | const symbolStr = item.symbol || '--' |
| | | const iconImg = item.symbol_data || (symbolStr.includes('/') ? symbolStr.split('/')[0].toLowerCase() : symbolStr.replace(/USDT$/i, '').toLowerCase()) || symbolStr.toLowerCase() |
| | | return { |
| | | symboltxt: symboltxt, |
| | | symboltxt, |
| | | symbol: symbolStr, |
| | | type: type, |
| | | iconImg: iconImg, |
| | | type, |
| | | iconImg, |
| | | price: basePrice.toFixed(4), |
| | | change: changeRatio, |
| | | sellPrice: sellPrice, |
| | | buyPrice: buyPrice, |
| | | klineData: klineData |
| | | sellPrice, |
| | | buyPrice, |
| | | klineData |
| | | } |
| | | }) |
| | | } else { |
| | |
| | | tradingPairs.value = [] |
| | | } |
| | | } |
| | | |
| | | // 监听 tab 切换 |
| | | watch(activeTab, () => { |
| | | fetchTradingData() |
| | | }) |
| | | |
| | | // 根据当前价与涨跌幅生成小型 K 线数据,每根 [open, close, low, high] |
| | | function generateMiniKlineData(basePrice, changeRatio) { |