| | |
| | | <template> |
| | | <div class="quotes-market-page"> |
| | | <!-- Top Tabs --> |
| | | <div class="market-tabs"> |
| | | <!-- <div class="tab-item" :class="{ active: activeTab === 'optional' }" @click="activeTab = 'optional'"> |
| | | {{ $t('自选') }} |
| | | </div> --> |
| | | <div class="tab-item" :class="{ active: activeTab === 'forex' }" @click="activeTab = 'forex'"> |
| | | {{ $t('外汇') }} |
| | | </div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'crypto' }" @click="activeTab = 'crypto'"> |
| | | <div class="market-tabs market-tabs--single"> |
| | | <div class="tab-item active"> |
| | | {{ $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> |
| | | </div> |
| | | |
| | |
| | | <van-list v-model:loading="marketLoading" :finished="marketFinished" :immediate-check="false" |
| | | :scroll-target="marketListRef" :finished-text="$t('没有更多了') || '没有更多了'" @load="loadMoreMarket"> |
| | | <div class="pair-item" v-for="pair in tradingPairs" :key="pair.symbol" |
| | | @click="goToOptions(pair.symbol, pair.type)"> |
| | | @click="goToOptions(pair.symbol)"> |
| | | <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() }} |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { useRouter } from 'vue-router' |
| | | import { List as VanList } from 'vant' |
| | |
| | | import { OPCIONA_LIST } from '@/store/types.store' |
| | | import { IMG_PATH } from '@/config' |
| | | import MiniKlineChart from '@/components/MiniKlineChart/index.vue' |
| | | |
| | | // 外汇货币代码 -> 国旗图国家/地区代码(与首页一致) |
| | | 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' |
| | | |
| | | const { t } = useI18n() |
| | | const router = useRouter() |
| | |
| | | const marketInitialLoading = ref(false) // tab 切换时首屏请求中,避免 @load 重复请求 pageNo=1 |
| | | const MARKET_PAGE_SIZE = 10 |
| | | |
| | | // 从外汇对取基础货币代码,与首页一致 |
| | | 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 不展示图标;自选按 type 判断 |
| | | function getPairIconUrl(pair) { |
| | | if (!pair) return '' |
| | | const tab = activeTab.value |
| | | if (tab === 'stock' || tab === 'etf') return '' |
| | | if (tab === 'optional' && (pair.type === 'US-stocks' || pair.type === 'indices')) return '' |
| | | if (tab === 'forex') { |
| | | const code = CURRENCY_TO_FLAG[getForexBaseCurrency(pair.symbol)] |
| | | return code ? `${FLAG_CDN}/${code}.png` : '' |
| | | } |
| | | 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` : '' |
| | | } |
| | | |
| | | // 根据当前价与涨跌幅生成小型 K 线数据,与首页一致 |
| | |
| | | |
| | | // 获取交易数据;pageNo 页码,append 是否追加 |
| | | const fetchTradingData = async (pageNo = 1, append = false) => { |
| | | 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 = 'cryptos' |
| | | |
| | | try { |
| | | const params = { |
| | | type: type, |
| | | pageNo: pageNo, |
| | | pageSize: MARKET_PAGE_SIZE |
| | | } |
| | | if (category) { |
| | | params.category = category |
| | | } |
| | | |
| | | const data = await _getRealtimeByType(params) |
| | |
| | | } |
| | | |
| | | // 跳转到交易页 Options,与首页一致:/trade/options?symbol=xxx&activeTab=xxx |
| | | function goToOptions(symbol, type) { |
| | | function goToOptions(symbol) { |
| | | if (!symbol) return |
| | | const tabMap = { crypto: 'cryptos', etf: 'indices', stock: 'US-stocks', forex: 'forex', optional: 'optional' } |
| | | const activeTabValue = type || tabMap[activeTab.value] || 'cryptos' |
| | | router.push({ |
| | | path: '/trade/options', |
| | | query: { symbol, activeTab: activeTabValue } |
| | | query: { symbol, activeTab: 'cryptos' } |
| | | }) |
| | | } |
| | | |
| | |
| | | marketInitialLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // 监听 tab 切换 |
| | | watch(activeTab, () => { |
| | | fetchData() |
| | | }) |
| | | |
| | | // 处理列表项点击 |
| | | const handleItemClick = (pair) => { |