Browse Source

新增行情

zhaowenkang/feature-20251028181547-行情页面
zhaowenkang 4 weeks ago
parent
commit
aa8f3ddc16
  1. 5
      common/util.js
  2. 28
      pages.json
  3. 655
      pages/home/chartExample.vue
  4. 493
      pages/home/countryMarket.vue
  5. 301
      pages/home/forexMetals.vue
  6. 323
      pages/home/globalIndex.vue
  7. 275
      pages/home/marketDetail.vue
  8. 733
      pages/home/marketOverview.vue
  9. 629
      pages/home/marketSituation.vue
  10. BIN
      static/marketSituation-image/cool.png
  11. BIN
      static/marketSituation-image/hot.png
  12. BIN
      static/marketSituation-image/warm.png

5
common/util.js

@ -1,6 +1,6 @@
var util = {} var util = {}
util.data = {} util.data = {}
util.data.base_url = 'https://dbqb.nfdxy.net/devApi'
util.data.base_url = 'https://hwjb.homilychart.com/testApi'
// util.data.base_url = 'https://dbqb.nfdxy.net/prodApi' // util.data.base_url = 'https://dbqb.nfdxy.net/prodApi'
// AJAX 请求方法 // AJAX 请求方法
@ -15,7 +15,8 @@ util.request = (url, callback, data = {}, failCallback) => {
'content-type': 'application/json', 'content-type': 'application/json',
'version': uni.getSystemInfoSync().appVersion, 'version': uni.getSystemInfoSync().appVersion,
'client': uni.getSystemInfoSync().platform == 'ios' ? 'ios' : 'android', 'client': uni.getSystemInfoSync().platform == 'ios' ? 'ios' : 'android',
'token': uni.getStorageSync('token')
'token': uni.getStorageSync('token'),
'deviceId': uni.getSystemInfoSync().deviceId
}, },
sslVerify: false, sslVerify: false,
success: callback, success: callback,

28
pages.json

@ -98,7 +98,33 @@
} }
}, },
{ {
"path": "pages/home/marketSituation",
"path": "pages/marketSituation/marketSituation",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/chartExample",
"style": {
"navigationBarTitleText": "图表示例",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/marketSituation/globalIndex",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/marketDetail",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,

655
pages/home/chartExample.vue

@ -0,0 +1,655 @@
<template>
<view style="width: 750rpx; height: 750rpx;">
<l-echart ref="chartRef" @finished="initChart"></l-echart>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const chartRef = ref(null)
// window.innerWidth
const systemInfo = uni.getSystemInfoSync()
const screenWidth = ref(systemInfo.screenWidth || 375) // 375px
// 30
const generateAIGoldBullData = () => {
const data = []
for (let i = 0; i < 30; i++) {
// [, , , , , ]
const buySignal = Math.random() > 0.7 ? 1 : 0 // 30%
const sellSignal = Math.random() > 0.8 ? 1 : 0 // 20%
const holdSignal = Math.random() > 0.5 ? 1 : 0 // 50%
const strength = Math.floor(Math.random() * 3) + 1 // 1-3
const volume = Math.floor(Math.random() * 2000) + 500 // 500-2500
data.push([i, buySignal, sellSignal, holdSignal, strength, volume])
}
return data
}
//
var option
const AIGoldBull = ref({
JN: generateAIGoldBullData()
})
//
const t = ref({
suoxie: 'zh',
tianxian: '天线',
feixian: '飞线',
zhoongxian: '中线',
liuxian: '流线',
Klinetext_5: 'K线5',
Klinetext_6: 'K线6',
maipan: '买盘',
maipan1: '卖盘'
})
// 30K线 [, , , , ]
const generateKLineData = () => {
const data = []
let basePrice = 2450 //
for (let i = 0; i < 30; i++) {
const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
//
const volatility = (Math.random() - 0.5) * 50 // ±25
const open = basePrice + volatility
const highVolatility = Math.random() * 30 + 10 // 10-40
const lowVolatility = Math.random() * 30 + 10 // 10-40
const high = Math.max(open, open + highVolatility)
const low = Math.min(open, open - lowVolatility)
const closeVolatility = (Math.random() - 0.5) * 20
const close = Math.max(low, Math.min(high, open + closeVolatility))
data.push([date,
Math.round(open * 100) / 100,
Math.round(high * 100) / 100,
Math.round(low * 100) / 100,
Math.round(close * 100) / 100
])
basePrice = close //
}
return data
}
// 30 [, 1, 2, 1, 2, 3]
const generateWaveVolData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
//
const vol1 = Math.floor(Math.random() * 2000) + 800 // 800-2800
const vol2 = Math.floor(Math.random() * 1500) + 600 // 600-2100
//
const indicator1 = Math.floor(Math.random() * 30) + 40 // 40-70
const indicator2 = Math.floor(Math.random() * 40) + 50 // 50-90
const indicator3 = Math.floor(Math.random() * 35) + 60 // 60-95
data.push([date, vol1, vol2, indicator1, indicator2, indicator3])
}
return data
}
// 30线 [MA5, MA10, MA20, MA30]
const generateFTLineData = () => {
const data = []
let ma5Base = 2450
let ma10Base = 2445
let ma20Base = 2440
let ma30Base = 2435
for (let i = 0; i < 30; i++) {
// 线
ma5Base += (Math.random() - 0.5) * 10
ma10Base += (Math.random() - 0.5) * 8
ma20Base += (Math.random() - 0.5) * 6
ma30Base += (Math.random() - 0.5) * 4
data.push([
Math.round(ma5Base * 100) / 100,
Math.round(ma10Base * 100) / 100,
Math.round(ma20Base * 100) / 100,
Math.round(ma30Base * 100) / 100
])
}
return data
}
//
const mockKLineData = generateKLineData()
const mockWaveVolData = generateWaveVolData()
const mockFTLineData = generateFTLineData()
// RSI ()
const generateRSIData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const rsi = Math.random() * 60 + 20 // RSI20-80
data.push(Math.round(rsi * 100) / 100)
}
return data
}
// MACD
const generateMACDData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const macd = (Math.random() - 0.5) * 20 // MACD-1010
const signal = (Math.random() - 0.5) * 15 // 线
const histogram = macd - signal //
data.push([
Math.round(macd * 100) / 100,
Math.round(signal * 100) / 100,
Math.round(histogram * 100) / 100
])
}
return data
}
//
const generateBollingerData = () => {
const data = []
let middleLine = 2450
for (let i = 0; i < 30; i++) {
middleLine += (Math.random() - 0.5) * 10
const upperBand = middleLine + Math.random() * 30 + 20 //
const lowerBand = middleLine - Math.random() * 30 - 20 //
data.push([
Math.round(upperBand * 100) / 100,
Math.round(middleLine * 100) / 100,
Math.round(lowerBand * 100) / 100
])
}
return data
}
//
const generateVolumeAnalysisData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const buyVolume = Math.floor(Math.random() * 1500) + 500 //
const sellVolume = Math.floor(Math.random() * 1500) + 500 //
const netVolume = buyVolume - sellVolume //
data.push([buyVolume, sellVolume, netVolume])
}
return data
}
//
const generateMarketSentimentData = () => {
const sentiments = ['极度恐慌', '恐慌', '中性', '贪婪', '极度贪婪']
const data = []
for (let i = 0; i < 30; i++) {
const sentimentIndex = Math.floor(Math.random() * 100) // 0-100
const sentimentLabel = sentiments[Math.floor(sentimentIndex / 20)]
data.push({
date: new Date(2024, 0, i + 1).toISOString().split('T')[0],
index: sentimentIndex,
label: sentimentLabel,
fearGreedRatio: Math.random() * 100
})
}
return data
}
//
const generateNewsEventsData = () => {
const events = [
'美联储利率决议',
'非农就业数据发布',
'通胀数据公布',
'地缘政治紧张',
'央行政策变化',
'经济数据超预期',
'市场技术突破',
'大宗商品价格波动'
]
const data = []
for (let i = 0; i < 10; i++) { // 10
const randomDay = Math.floor(Math.random() * 30) + 1
const event = events[Math.floor(Math.random() * events.length)]
const impact = Math.floor(Math.random() * 5) + 1 // 1-5
data.push({
date: new Date(2024, 0, randomDay).toISOString().split('T')[0],
event: event,
impact: impact,
type: Math.random() > 0.5 ? 'positive' : 'negative'
})
}
return data.sort((a, b) => new Date(a.date) - new Date(b.date))
}
//
const generatePricePredictionData = () => {
const data = []
let currentPrice = 2450
for (let i = 0; i < 7; i++) { // 7
const date = new Date(2024, 1, i + 1).toISOString().split('T')[0] // 2
// AI
const prediction = currentPrice + (Math.random() - 0.5) * 100
const confidence = Math.random() * 40 + 60 // 60-100%
const upperBound = prediction + Math.random() * 50
const lowerBound = prediction - Math.random() * 50
data.push({
date: date,
predicted_price: Math.round(prediction * 100) / 100,
confidence: Math.round(confidence),
upper_bound: Math.round(upperBound * 100) / 100,
lower_bound: Math.round(lowerBound * 100) / 100
})
currentPrice = prediction
}
return data
}
//
const extractedDrawData = {
KLine20: mockKLineData,
WAVEVOL: mockWaveVolData,
FTLINE: mockFTLineData,
RSI: generateRSIData(),
MACD: generateMACDData(),
BOLLINGER: generateBollingerData(),
VOLUME_ANALYSIS: generateVolumeAnalysisData(),
MARKET_SENTIMENT: generateMarketSentimentData(),
NEWS_EVENTS: generateNewsEventsData(),
PRICE_PREDICTION: generatePricePredictionData()
}
const fnShowEcharts4 = (extractedDrawData) => {
const splitData = (b) => {
const a = JSON.parse(JSON.stringify(b))
let categoryData = []
let values = []
for (let i = 0; i < a.length; i++) {
categoryData.push(a[i].splice(0, 1)[0])
values.push(a[i])
}
return {
categoryData,
values
}
}
var bodongliang = splitData(extractedDrawData.WAVEVOL)
function bodongliangData(values, i) {
return values.map((subArray) => subArray[i])
}
function calculateMA(index, data) {
let result = []
if (data.FTLINE) {
data.FTLINE.forEach((item) => {
result.push(item[index])
})
}
return result
}
function vwToPx(vw) {
return (screenWidth.value * vw) / 100
}
var dealData = splitData(extractedDrawData.KLine20)
var dealGnBullData = AIGoldBull.value.JN
const textEcharts = t.value
const firstLegend = computed(() => {
if (screenWidth.value < 768) {
if (textEcharts.suoxie === 'en' || textEcharts.suoxie === 'th') {
return '2%'
} else if (textEcharts.suoxie === 'kr') {
return '2%'
} else {
return '2%'
}
} else {
return textEcharts.suoxie === 'en' ||
textEcharts.suoxie === 'th' ||
textEcharts.suoxie === 'kr'
? '9%'
: '9%'
}
})
const processBarData = (data) => {
const barData = []
data.forEach((item) => {
let color
switch (item[4]) {
case 1:
color = '#13E113'
break
case 2:
color = '#FF0E00'
break
case 3:
color = '#0000FE'
break
case 4:
color = '#1397FF'
break
}
barData.push({
value: item[5],
itemStyle: {
normal: {
color: color
}
}
})
})
return { barData }
}
const { barData } = processBarData(dealGnBullData)
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
backgroundColor: 'rgba(119, 120, 125, 0.6)',
borderWidth: 1,
borderColor: '#77787D',
padding: 10,
textStyle: {
color: '#fff'
}
},
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
label: {
backgroundColor: '#77787D'
}
},
toolbox: {
show: false
},
grid: [
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '10%' : '12%',
height: screenWidth.value > 768 ? '35%' : '34%',
containLabel: false
},
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '48%' : '48%',
height: screenWidth.value > 768 ? '19%' : '21%',
containLabel: false
},
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '70%' : '71%',
height: screenWidth.value > 768 ? '19%' : '21%',
containLabel: false
}
],
xAxis: [
{
type: 'category',
data: dealData.categoryData,
boundaryGap: true,
axisLine: { onZero: false },
splitLine: { show: false },
min: 'dataMin',
max: 'dataMax',
axisPointer: {
z: 100,
label: {
show: false //
}
},
axisLine: {
lineStyle: {
color: 'black'
}
}, //
axisLabel: { show: false },
axisTick: { show: false }
},
{
type: 'category',
gridIndex: 1,
data: dealData.categoryData,
boundaryGap: true,
axisPointer: {
z: 100,
label: {
show: false //
}
},
axisLine: { lineStyle: { color: 'black' } },
axisLabel: {
show: false,
interval: 'auto'
},
axisTick: { show: false }
},
{
type: 'category',
gridIndex: 2,
data: dealData.categoryData,
boundaryGap: true,
axisLine: { lineStyle: { color: 'black' } },
axisLabel: {
show: true,
interval: 'auto',
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisTick: { show: false }
}
],
yAxis: [
{
scale: true,
gridIndex: 0,
position: 'left',
axisLabel: {
inside: false,
align: 'right',
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisLine: {
show: true,
lineStyle: {
fontSize: '',
color: 'black'
}
},
axisTick: { show: false },
splitLine: { show: false }
},
{
scale: true,
gridIndex: 1,
splitNumber: 4,
min: 0,
minInterval: 1,
axisLabel: {
show: true,
fontSize: screenWidth.value > 768 ? 15 : 9,
margin: 8,
},
axisLine: { show: true, lineStyle: { color: 'black' } },
axisTick: { show: false },
splitLine: { show: true, lineStyle: { type: 'dashed' } },
boundaryGap: ['20%', '20%']
},
{
scale: true,
gridIndex: 2,
splitNumber: 2,
axisLabel: {
show: true,
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisLine: { show: true, lineStyle: { color: 'black' } },
axisTick: { show: false },
splitLine: { show: false }
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1, 2],
start: 50,
end: 100
},
{
show: true,
xAxisIndex: [0, 1, 2],
type: 'slider',
start: 50,
end: 100
}
],
series: [
{
type: 'candlestick',
name: '日K',
xAxisIndex: 0,
yAxisIndex: 0,
data: dealData.values,
itemStyle: {
normal: {
color0: 'red',
color: 'green',
borderColor0: 'red',
borderColor: 'green'
}
},
gridIndex: 1
},
{
name: '成交量',
type: 'bar',
barWidth: '70%',
xAxisIndex: 1,
yAxisIndex: 1,
data: barData,
},
// {
// name: textEcharts.feixian,
// type: 'line',
// data: calculateMA(1, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#00a32e',
// lineStyle: {
// color: '#00a32e',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.zhoongxian,
// type: 'line',
// data: calculateMA(2, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#de0000',
// lineStyle: {
// color: '#de0000',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.tianxian,
// type: 'line',
// data: calculateMA(3, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#ffb300',
// lineStyle: {
// color: '#ffb300',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.liuxian,
// type: 'line',
// data: calculateMA(4, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#00c8ff',
// lineStyle: {
// color: '#00c8ff',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
]
}
initChart()
}
//
onMounted(() => {
//
fnShowEcharts4(extractedDrawData)
})
//
const initChart = async () => {
if (!chartRef.value) return
try {
const chart = await chartRef.value.init(echarts)
chart.setOption(option)
} catch (error) {
console.error('图表初始化失败:', error)
}
}
</script>

493
pages/home/countryMarket.vue

@ -0,0 +1,493 @@
<template>
<view class="content">
<!-- 市场子Tab -->
<view class="sub_tabs">
<view v-for="(tab, i) in marketTabs" :key="tab" :class="['tab_item', i === activeTabIndex ? 'active' : '']"
@click="switchTab(i)">
<text>{{ tab }}</text>
</view>
</view>
<!-- 大盘指数 -->
<view class="section">
<view class="section_header">
<text class="section_title">大盘指数</text>
<text class="section_action" @click="viewMore('indices')">查看更多 ></text>
</view>
<view class="indices_grid">
<view v-for="(index, i) in countryInfo.mainIndices" :key="i" class="index_item">
<IndexCard :flagIcon="countryInfo.flag" :indexName="index.name" :currentPrice="index.price"
:changeAmount="index.change" :changePercent="index.changePercent" :isRising="index.isRising" />
</view>
</view>
<!-- 今日市场情绪温度 -->
<view class="sentiment">
<view class="section_subtitle">
<text>今日市场情绪温度</text>
</view>
<view class="meters">
<view class="meter_item" v-for="(m, i) in sentimentMeters" :key="i">
<image class="meter_icon" :class="m.theme" :src="selectIcons[m.theme]" mode="aspectFit"></image>
<view class="meter_info">
<text class="meter_value" :class="m.theme">{{ m.value }}°C</text>
<text class="meter_label" :class="m.theme">{{ m.label }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 板块 -->
<view class="section">
<view class="section_header">
<text class="section_title">板块</text>
<text class="section_action" @click="viewMore('sectors')">查看更多 ></text>
</view>
<view class="sectors_grid">
<view v-for="(sec, i) in sectors" :key="i" class="sector_item">
<view class="sector_header">
<text class="sector_name">{{ sec.name }}</text>
<text :class="['sector_change', sec.isRising ? 'rising' : 'falling']">
{{ sec.change }}
</text>
</view>
<view class="sector_price">{{ sec.price }}</view>
</view>
</view>
</view>
<!-- 股票 -->
<view class="section">
<view class="section_header">
<text class="section_title">股票</text>
<text class="section_action" @click="viewMore('stocks')">查看更多 ></text>
</view>
<view class="table">
<view class="table_header">
<text class="cell name">名称</text>
<text class="cell price">最新</text>
<text class="cell change">涨幅</text>
</view>
<view class="table_row" v-for="(stk, i) in stocks" :key="i">
<view class="cell name">
<text class="stk_name">{{ stk.name }}</text>
<text class="stk_code">{{ stk.code }}</text>
</view>
<view class="cell price">
<text class="stk_price">{{ stk.price }}</text>
</view>
<view class="cell change">
<text :class="['stk_change', stk.isRising ? 'rising' : 'falling']">
{{ stk.change }}
</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom_safe_area"></view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import IndexCard from '../../components/IndexCard.vue'
// Tab
const marketTabs = ['全部', '美股', '纽交所', '纳斯达克']
const activeTabIndex = ref(0)
const switchTab = (i) => {
activeTabIndex.value = i
}
//
const sentimentMeters = [
{ value: 90, label: '道琼斯', theme: 'hot' },
{ value: 60, label: '纳斯达克', theme: 'warm' },
{ value: 20, label: '标普500', theme: 'cool' }
]
//
const selectIcons = {
hot: '/static/marketSituation-image/hot.png',
warm: '/static/marketSituation-image/warm.png',
cool: '/static/marketSituation-image/cool.png'
}
// Props
const props = defineProps({
countryId: {
type: Number,
required: true
}
})
// /
const countryInfoMap = {
2: { //
name: '新加坡',
flag: '🇸🇬',
isMarketOpen: true,
mainIndices: [
{ name: '海峡时报指数', price: '3,234.56', change: '+12.34', changePercent: '+0.38%', isRising: true },
{ name: 'FTSE ST Mid Cap', price: '1,234.56', change: '-5.67', changePercent: '-0.46%', isRising: false }
],
hotStocks: [
{ name: '星展银行', code: 'D05.SI', price: '35.20', change: '+0.15', isRising: true },
{ name: '华侨银行', code: 'O39.SI', price: '13.45', change: '-0.05', isRising: false }
]
},
3: { // 西
name: '马来西亚',
flag: '🇲🇾',
isMarketOpen: false,
mainIndices: [
{ name: '富时大马KLCI指数', price: '1,567.89', change: '+8.90', changePercent: '+0.57%', isRising: true }
],
hotStocks: [
{ name: '马来亚银行', code: '1155.KL', price: '9.85', change: '+0.10', isRising: true },
{ name: '大众银行', code: '1295.KL', price: '4.32', change: '-0.02', isRising: false }
]
},
4: { // 西
name: '印度尼西亚',
flag: '🇮🇩',
isMarketOpen: true,
mainIndices: [
{ name: '雅加达综合指数', price: '7,234.56', change: '+45.67', changePercent: '+0.63%', isRising: true }
],
hotStocks: []
},
5: { //
name: '美国',
flag: '🇺🇸',
isMarketOpen: false,
mainIndices: [
{ name: '道琼斯', price: '45,757.90', change: '-125.22', changePercent: '-0.27%', isRising: false },
{ name: '纳斯达克', price: '22,333.96', change: '+125.22', changePercent: '+0.47%', isRising: true },
{ name: '标普500', price: '6,606.08', change: '+125.22', changePercent: '+0.27%', isRising: true }
],
hotStocks: [
{ name: '苹果', code: 'AAPL', price: '195.89', change: '+2.34', isRising: true },
{ name: '微软', code: 'MSFT', price: '378.85', change: '-1.23', isRising: false }
]
}
}
//
const countryInfo = computed(() => {
return countryInfoMap[props.countryId] || {
name: '未知地区',
flag: '🌍',
isMarketOpen: false,
mainIndices: [],
hotStocks: []
}
})
//
const sectors = computed(() => {
return countryInfoMap[props.countryId]?.sectors || []
})
const stocks = computed(() => {
return countryInfoMap[props.countryId]?.stocks || []
})
//
const viewMore = (type) => {
// type
// indices/sectors/stocks
// uni.navigateTo({ url: `/pages/marketSituation/${type}List` })
}
</script>
<style scoped>
.content {
padding: 0 20rpx 20rpx 20rpx;
}
/* 子Tab */
.sub_tabs {
display: flex;
gap: 16rpx;
padding: 0 20rpx 20rpx 20rpx;
}
.tab_item {
padding: 6rpx 20rpx;
border-radius: 5rpx;
background: #f5f5f5;
color: #666;
font-size: 24rpx;
}
.tab_item.active {
background: #ff4444;
color: #fff;
}
.section {
padding: 20rpx;
border-radius: 16rpx;
}
.section_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section_title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.section_action {
font-size: 24rpx;
color: #999;
}
.indices_grid {
padding: 20rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
background-color: #F6F6F6;
}
/* 情绪温度 */
.sentiment {
background-color: #F6F6F6;
padding: 0 20rpx 20rpx 20rpx;
}
.section_subtitle {
font-size: 24rpx;
color: #000000;
padding: 20rpx 0;
}
.meters {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.meter_item {
display: flex;
align-items: center;
/* padding: 10rpx; */
background: #ffffff;
border-radius: 12rpx;
}
.meter_item image {
width: 100rpx;
height: 100rpx;
}
.meter_info {
display: flex;
flex-direction: column;
}
.meter_value {
font-size: 36rpx;
}
.meter_value.hot {
color: #ff6b6b;
}
.meter_value.warm {
color: #ffd166;
}
.meter_value.cool {
color: #60a5fa;
}
.meter_label {
font-size: 22rpx;
}
.meter_label.hot {
color: #ff6b6b;
}
.meter_label.warm {
color: #ffd166;
}
.meter_label.cool {
color: #60a5fa;
}
/* 板块 */
.sectors_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.sector_item {
background: #fafafa;
border-radius: 12rpx;
padding: 16rpx;
}
.sector_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.sector_name {
font-size: 24rpx;
color: #333;
}
.sector_change {
font-size: 22rpx;
}
.sector_change.rising {
color: #e74c3c;
}
.sector_change.falling {
color: #27ae60;
}
.sector_price {
font-size: 24rpx;
color: #666;
}
/* 股票表 */
.table {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.table_header,
.table_row {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
padding: 18rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.table_header {
background: #fafafa;
}
.cell.name {
display: flex;
flex-direction: column;
}
.stk_name {
font-size: 26rpx;
color: #333;
}
.stk_code {
font-size: 22rpx;
color: #999;
}
.stk_price {
font-size: 26rpx;
color: #333;
}
.stk_change {
font-size: 24rpx;
}
.stk_change.rising {
color: #e74c3c;
}
.stk_change.falling {
color: #27ae60;
}
.index_item {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.hot_stocks {
margin-bottom: 30rpx;
}
.stocks_list {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.stock_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.stock_item:last-child {
border-bottom: none;
}
.stock_info {
flex: 1;
}
.stock_name {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.stock_code {
font-size: 24rpx;
color: #999;
}
.stock_price {
text-align: right;
}
.price {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.change {
font-size: 24rpx;
}
.change.rising {
color: #e74c3c;
}
.change.falling {
color: #27ae60;
}
.bottom_safe_area {
height: 120rpx;
}
</style>

301
pages/home/forexMetals.vue

@ -0,0 +1,301 @@
<template>
<view class="content">
<view class="section" v-if="type === 'forex'">
<view class="section_title">
<text class="title_icon">💱</text>
<text>外汇市场</text>
</view>
<view class="forex_grid">
<view v-for="(item, index) in forexData" :key="index" class="forex_item">
<view class="forex_pair">
<text class="base_currency">{{ item.base }}</text>
<text class="separator">/</text>
<text class="quote_currency">{{ item.quote }}</text>
</view>
<view class="forex_price">
<text class="price">{{ item.price }}</text>
<text :class="['change', item.isRising ? 'rising' : 'falling']">
{{ item.change }}
</text>
</view>
</view>
</view>
</view>
<view class="section" v-if="type === 'metals'">
<view class="section_title">
<text class="title_icon">🥇</text>
<text>贵金属</text>
</view>
<view class="metals_grid">
<view v-for="(item, index) in metalsData" :key="index" class="metal_item">
<view class="metal_info">
<text class="metal_icon">{{ item.icon }}</text>
<text class="metal_name">{{ item.name }}</text>
</view>
<view class="metal_price">
<text class="price">{{ item.price }}</text>
<text class="unit">{{ item.unit }}</text>
<text :class="['change', item.isRising ? 'rising' : 'falling']">
{{ item.change }}
</text>
</view>
</view>
</view>
</view>
<!-- 市场动态 -->
<view class="market_news">
<view class="section_title">
<text class="title_icon">📰</text>
<text>市场动态</text>
</view>
<view class="news_list">
<view v-for="(news, index) in newsData" :key="index" class="news_item">
<view class="news_content">
<text class="news_title">{{ news.title }}</text>
<text class="news_time">{{ news.time }}</text>
</view>
<view class="news_impact" :class="news.impact">
<text>{{ news.impactText }}</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom_safe_area"></view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// Props
const props = defineProps({
countryId: {
type: Number,
required: true
}
})
//
const type = computed(() => {
return props.countryId === 11 ? 'forex' : 'metals'
})
//
const forexData = ref([
{ base: 'USD', quote: 'CNY', price: '7.2456', change: '+0.0123', isRising: true },
{ base: 'EUR', quote: 'USD', price: '1.0876', change: '-0.0034', isRising: false },
{ base: 'GBP', quote: 'USD', price: '1.2654', change: '+0.0087', isRising: true },
{ base: 'USD', quote: 'JPY', price: '149.87', change: '+0.45', isRising: true },
{ base: 'AUD', quote: 'USD', price: '0.6543', change: '-0.0021', isRising: false },
{ base: 'USD', quote: 'SGD', price: '1.3456', change: '+0.0012', isRising: true }
])
//
const metalsData = ref([
{ icon: '🥇', name: '黄金', price: '2,034.56', unit: 'USD/盎司', change: '+12.34', isRising: true },
{ icon: '🥈', name: '白银', price: '24.87', unit: 'USD/盎司', change: '-0.23', isRising: false },
{ icon: '⚪', name: '铂金', price: '987.65', unit: 'USD/盎司', change: '+5.67', isRising: true },
{ icon: '⚫', name: '钯金', price: '1,234.56', unit: 'USD/盎司', change: '-8.90', isRising: false }
])
//
const newsData = ref([
{
title: '美联储暗示可能暂停加息',
time: '2小时前',
impact: 'high',
impactText: '高影响'
},
{
title: '欧洲央行维持利率不变',
time: '4小时前',
impact: 'medium',
impactText: '中影响'
},
{
title: '黄金价格创新高',
time: '6小时前',
impact: 'medium',
impactText: '中影响'
},
{
title: '美元指数小幅下跌',
time: '8小时前',
impact: 'low',
impactText: '低影响'
}
])
</script>
<style scoped>
.content {
padding: 20rpx;
}
.section {
margin-bottom: 40rpx;
}
.section_title {
display: flex;
align-items: center;
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.title_icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.forex_grid, .metals_grid {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.forex_item, .metal_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.forex_item:last-child, .metal_item:last-child {
border-bottom: none;
}
.forex_pair {
display: flex;
align-items: center;
}
.base_currency {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.separator {
font-size: 24rpx;
color: #999;
margin: 0 8rpx;
}
.quote_currency {
font-size: 28rpx;
color: #666;
}
.forex_price, .metal_price {
text-align: right;
}
.price {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.unit {
font-size: 20rpx;
color: #999;
margin-left: 8rpx;
}
.change {
font-size: 24rpx;
}
.change.rising {
color: #e74c3c;
}
.change.falling {
color: #27ae60;
}
.metal_info {
display: flex;
align-items: center;
}
.metal_icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.metal_name {
font-size: 28rpx;
color: #333;
}
.market_news {
margin-bottom: 30rpx;
}
.news_list {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.news_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.news_item:last-child {
border-bottom: none;
}
.news_content {
flex: 1;
}
.news_title {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.news_time {
font-size: 24rpx;
color: #999;
}
.news_impact {
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 20rpx;
color: #fff;
}
.news_impact.high {
background: #e74c3c;
}
.news_impact.medium {
background: #f39c12;
}
.news_impact.low {
background: #95a5a6;
}
.bottom_safe_area {
height: 120rpx;
}
</style>

323
pages/home/globalIndex.vue

@ -1,5 +1,3 @@
<!-- @format -->
<template> <template>
<view class="main"> <view class="main">
<!-- 固定头部 --> <!-- 固定头部 -->
@ -9,8 +7,11 @@
<image src="/static/marketSituation-image/back.png" mode=""></image> <image src="/static/marketSituation-image/back.png" mode=""></image>
</view> </view>
<view class="header_input_wrapper"> <view class="header_input_wrapper">
<image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" />
<image class="search_icon" src="/static/marketSituation-image/search.png" mode=""
@click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索"
placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue"
@input="onSearchInput" @confirm="onSearchConfirm" />
</view> </view>
<view class="header_icons"> <view class="header_icons">
<view class="header_icon" @click="selected"> <view class="header_icon" @click="selected">
@ -24,7 +25,7 @@
<view class="warn"> <view class="warn">
<image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image> <image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
<view class="warn_text_container"> <view class="warn_text_container">
<text :class="warnTextClass">{{ $t("marketSituation.warn") }}</text>
<text :class="warnTextClass">{{ $t('marketSituation.warn') }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -42,7 +43,10 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" />
</view> </view>
</view> </view>
</view> </view>
@ -58,7 +62,10 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in asiaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in asiaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" />
</view> </view>
</view> </view>
</view> </view>
@ -74,7 +81,10 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in americaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in americaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" />
</view> </view>
</view> </view>
</view> </view>
@ -89,246 +99,230 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed, nextTick, watch } from "vue";
import footerBar from "../../components/footerBar.vue";
import IndexCard from "../../components/IndexCard.vue";
import { ref, onMounted, computed, nextTick, watch } from 'vue'
import footerBar from './footerBar.vue'
import IndexCard from '../../components/IndexCard.vue'
// //
const iSMT = ref(0); //
const contentHeight = ref(0);
const headerHeight = ref(0); //
const searchValue = ref(""); //
const isWarnTextOverflow = ref(false); // warn
const iSMT = ref(0) //
const contentHeight = ref(0)
const headerHeight = ref(0) //
const searchValue = ref('') //
const isWarnTextOverflow = ref(false) // warn
// warnclass // warnclass
const warnTextClass = computed(() => { const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? "warn_text scroll-active" : "warn_text";
});
return isWarnTextOverflow.value ? 'warn_text scroll-active' : 'warn_text'
})
// warn // warn
const checkWarnTextOverflow = () => { const checkWarnTextOverflow = () => {
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
const query = uni.createSelectorQuery();
const query = uni.createSelectorQuery()
// //
query.select(".warn_text_container").boundingClientRect();
query.select(".warn_text").boundingClientRect();
query.select('.warn_text_container').boundingClientRect()
query.select('.warn_text').boundingClientRect()
query.exec((res) => { query.exec((res) => {
const containerRect = res[0];
const textRect = res[1];
const containerRect = res[0]
const textRect = res[1]
if (!containerRect || !textRect) { if (!containerRect || !textRect) {
return;
return
} }
// //
const isOverflow = textRect.width > containerRect.width - 10;
const isOverflow = textRect.width > (containerRect.width - 10)
isWarnTextOverflow.value = isOverflow;
});
}, 500);
});
};
isWarnTextOverflow.value = isOverflow
})
}, 500)
})
}
// - // -
const asiachinaIndexes = ref([ const asiachinaIndexes = ref([
{ {
flagIcon: "/static/c1.png",
stockName: "上证指数",
stockCode: "1A0001",
currentPrice: "3933.96",
changeAmount: "+24.32",
changePercent: "+0.62%",
isRising: true,
flagIcon: '/static/c1.png',
indexName: '上证指数',
currentPrice: '3933.96',
changeAmount: '+24.32',
changePercent: '+0.62%',
isRising: true
}, },
{ {
flagIcon: "/static/c2.png",
stockName: "深证成指",
stockCode: "2A01",
currentPrice: "45757.90",
changeAmount: "-123.45",
changePercent: "-0.27%",
isRising: false,
flagIcon: '/static/c2.png',
indexName: '深证成指',
currentPrice: '45757.90',
changeAmount: '-123.45',
changePercent: '-0.27%',
isRising: false
}, },
{ {
flagIcon: "/static/c3.png",
stockName: "创业板指",
stockCode: "399006",
currentPrice: "6606.08",
changeAmount: "+89.76",
changePercent: "+1.38%",
isRising: true,
flagIcon: '/static/c3.png',
indexName: '创业板指',
currentPrice: '6606.08',
changeAmount: '+89.76',
changePercent: '+1.38%',
isRising: true
}, },
{ {
flagIcon: "/static/c4.png",
stockName: "沪深300",
stockCode: "1B0300",
currentPrice: "45757.90",
changeAmount: "-89.12",
changePercent: "-0.19%",
isRising: false,
flagIcon: '/static/c4.png',
indexName: 'HSI50',
currentPrice: '22333.96',
changeAmount: '+156.78',
changePercent: '+0.71%',
isRising: true
}, },
{ {
flagIcon: "/static/c5.png",
stockName: "上证50",
stockCode: "1B0011",
currentPrice: "45757.90",
changeAmount: "+234.56",
changePercent: "+0.52%",
isRising: true,
flagIcon: '/static/c5.png',
indexName: '沪深300',
currentPrice: '45757.90',
changeAmount: '-89.12',
changePercent: '-0.19%',
isRising: false
}, },
{ {
flagIcon: "/static/c6.png",
stockName: "科创50",
stockCode: "1B0688",
currentPrice: "22333.96",
changeAmount: "+156.78",
changePercent: "+0.71%",
isRising: true,
},
]);
flagIcon: '/static/c6.png',
indexName: '上证50',
currentPrice: '45757.90',
changeAmount: '+234.56',
changePercent: '+0.52%',
isRising: true
}
])
// //
const asiaIndexes = ref([ const asiaIndexes = ref([
{ {
flagIcon: "/static/c7.png",
stockName: "日经225",
stockCode: "noCode",
currentPrice: "28456.78",
changeAmount: "+234.56",
changePercent: "+0.83%",
isRising: true,
flagIcon: '/static/c7.png',
indexName: '日经225',
currentPrice: '28456.78',
changeAmount: '+234.56',
changePercent: '+0.83%',
isRising: true
}, },
{ {
flagIcon: "/static/c8.png",
stockName: "韩国KOSPI",
stockCode: "noCode",
currentPrice: "2567.89",
changeAmount: "-12.34",
changePercent: "-0.48%",
isRising: false,
flagIcon: '/static/c8.png',
indexName: '韩国KOSPI',
currentPrice: '2567.89',
changeAmount: '-12.34',
changePercent: '-0.48%',
isRising: false
}, },
{ {
flagIcon: "/static/c9.png",
stockName: "印度孟买",
stockCode: "noCode",
currentPrice: "65432.10",
changeAmount: "+456.78",
changePercent: "+0.70%",
isRising: true,
},
]);
flagIcon: '/static/c9.png',
indexName: '印度孟买',
currentPrice: '65432.10',
changeAmount: '+456.78',
changePercent: '+0.70%',
isRising: true
}
])
// //
const americaIndexes = ref([ const americaIndexes = ref([
{ {
flagIcon: "/static/c7.png",
stockName: "道琼斯指数",
stockCode: "noCode",
currentPrice: "34567.89",
changeAmount: "+123.45",
changePercent: "+0.36%",
isRising: true,
flagIcon: '/static/c7.png',
indexName: '道琼斯指数',
currentPrice: '34567.89',
changeAmount: '+123.45',
changePercent: '+0.36%',
isRising: true
}, },
{ {
flagIcon: "/static/c8.png",
stockName: "纳斯达克",
stockCode: "noCode",
currentPrice: "13456.78",
changeAmount: "-67.89",
changePercent: "-0.50%",
isRising: false,
flagIcon: '/static/c8.png',
indexName: '纳斯达克',
currentPrice: '13456.78',
changeAmount: '-67.89',
changePercent: '-0.50%',
isRising: false
}, },
{ {
flagIcon: "/static/c9.png",
stockName: "标普500",
stockCode: "noCode",
currentPrice: "4234.56",
changeAmount: "+23.45",
changePercent: "+0.56%",
isRising: true,
},
]);
flagIcon: '/static/c9.png',
indexName: '标普500',
currentPrice: '4234.56',
changeAmount: '+23.45',
changePercent: '+0.56%',
isRising: true
}
])
// //
const contentTopPosition = computed(() => { const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0;
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 100;
return statusBarHeight + currentHeaderHeight;
});
const statusBarHeight = iSMT.value || 0
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 100
return statusBarHeight + currentHeaderHeight
})
// //
const goBack = () => { const goBack = () => {
uni.navigateBack();
};
uni.navigateBack()
}
// //
const onSearchInput = (e) => { const onSearchInput = (e) => {
searchValue.value = e.detail.value;
};
searchValue.value = e.detail.value
}
// //
const clearSearch = () => { const clearSearch = () => {
searchValue.value = "";
};
searchValue.value = ''
}
// //
const viewMore = (market) => { const viewMore = (market) => {
console.log("查看更多:", market);
console.log('查看更多:', market)
uni.navigateTo({ uni.navigateTo({
url: `/pages/home/marketDetail?market=${market}`,
});
};
url: `/pages/marketSituation/marketDetail?market=${market}`
})
}
// //
const viewIndexDetail = (item) => { const viewIndexDetail = (item) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
console.log('查看指数详情:', item.indexName)
uni.showToast({
title: `查看 ${item.indexName} 详情`,
icon: 'none',
duration: 2000
})
// //
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
});
};
// uni.navigateTo({
// url: `/pages/detail/indexDetail?id=${item.id}`
// })
}
// //
onMounted(() => { onMounted(() => {
// //
const systemInfo = uni.getSystemInfoSync();
iSMT.value = systemInfo.statusBarHeight || 0;
const systemInfo = uni.getSystemInfoSync()
iSMT.value = systemInfo.statusBarHeight || 0
console.log("全球指数页面加载完成");
console.log('全球指数页面加载完成')
// header // header
uni
.createSelectorQuery()
.select(".header_fixed")
.boundingClientRect((rect) => {
uni.createSelectorQuery().select('.header_fixed').boundingClientRect((rect) => {
if (rect) { if (rect) {
headerHeight.value = rect.height;
console.log("Header实际高度:", headerHeight.value, "px");
headerHeight.value = rect.height
console.log('Header实际高度:', headerHeight.value, 'px')
} }
})
.exec();
}).exec()
// warn // warn
checkWarnTextOverflow();
});
checkWarnTextOverflow()
})
// headerHeightcontentHeight // headerHeightcontentHeight
watch(headerHeight, (newHeight) => { watch(headerHeight, (newHeight) => {
if (newHeight > 0) { if (newHeight > 0) {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const statusBarHeight = systemInfo.statusBarHeight || 0;
const footerHeight = 100;
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const statusBarHeight = systemInfo.statusBarHeight || 0
const footerHeight = 100
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight;
console.log("重新计算contentHeight:", contentHeight.value);
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight
console.log('重新计算contentHeight:', contentHeight.value)
} }
});
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -424,6 +418,7 @@ watch(headerHeight, (newHeight) => {
height: 40rpx; height: 40rpx;
} }
.warn { .warn {
display: flex; display: flex;
align-items: center; align-items: center;

275
pages/home/marketDetail.vue

@ -1,5 +1,3 @@
<!-- @format -->
<template> <template>
<view class="main"> <view class="main">
<!-- 自定义导航栏 --> <!-- 自定义导航栏 -->
@ -23,31 +21,36 @@
</view> </view>
<view class="header-item price-column" @click="sortByPrice"> <view class="header-item price-column" @click="sortByPrice">
<text class="header-text">最新</text> <text class="header-text">最新</text>
<text class="sort-icon">{{ sortType === "price" ? (sortOrder === "asc" ? "↑" : "↓") : "↕" }}</text>
<text class="sort-icon">{{ sortType === 'price' ? (sortOrder === 'asc' ? '↑' : '↓') : '↕' }}</text>
</view> </view>
<view class="header-item change-column" @click="sortByChange"> <view class="header-item change-column" @click="sortByChange">
<text class="header-text">涨幅</text> <text class="header-text">涨幅</text>
<text class="sort-icon">{{ sortType === "change" ? (sortOrder === "asc" ? "↑" : "↓") : "↕" }}</text>
<text class="sort-icon">{{ sortType === 'change' ? (sortOrder === 'asc' ? '↑' : '↓') : '↕' }}</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 内容区域 --> <!-- 内容区域 -->
<scroll-view class="content" :style="{ top: contentTopPosition + 'px' }" scroll-y="true"> <scroll-view class="content" :style="{ top: contentTopPosition + 'px' }" scroll-y="true">
<!-- 股票列表 --> <!-- 股票列表 -->
<view class="stock-list"> <view class="stock-list">
<view class="stock-row" v-for="(stock, index) in sortedStockList" :key="index" @click="viewStockDetail(stock)">
<view class="stock-row" v-for="(stock, index) in sortedStockList" :key="index"
@click="viewStockDetail(stock)">
<view class="stock-cell name-column"> <view class="stock-cell name-column">
<view class="stock-name">{{ stock.stockName }}</view>
<view class="stock-code">{{ stock.stockCode }}</view>
<view class="stock-name">{{ stock.name }}</view>
<view class="stock-code">{{ stock.code }}</view>
</view> </view>
<view class="stock-cell price-column"> <view class="stock-cell price-column">
<text class="stock-price" :class="stock.isRising ? 'rising' : 'falling'">
{{ typeof stock.price === "number" ? stock.price.toFixed(2) : stock.price }}
<text class="stock-price"
:class="stock.isRising ? 'rising' : 'falling'">
{{ typeof stock.price === 'number' ? stock.price.toFixed(2) : stock.price }}
</text> </text>
</view> </view>
<view class="stock-cell change-column"> <view class="stock-cell change-column">
<text class="stock-change" :class="stock.isRising ? 'rising' : 'falling'">
<text class="stock-change"
:class="stock.isRising ? 'rising' : 'falling'">
{{ stock.change || stock.changePercent }} {{ stock.change || stock.changePercent }}
</text> </text>
</view> </view>
@ -64,224 +67,218 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import footerBar from "@/components/footerBar.vue";
import { ref, computed, onMounted, watch } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import footerBar from '@/components/footerBar.vue'
// //
const iSMT = ref(0);
const headerHeight = ref(80);
const marketType = ref("america");
const marketTitle = ref("美洲");
const sortType = ref(""); // 'price' 'change'
const sortOrder = ref("desc"); // 'asc' 'desc'
const iSMT = ref(0)
const contentHeight = ref(0)
const headerHeight = ref(80)
const marketType = ref('america')
const marketTitle = ref('美洲')
const sortType = ref('') // 'price' 'change'
const sortOrder = ref('desc') // 'asc' 'desc'
// //
const stockList = ref([ const stockList = ref([
{ {
stockName: "Telecommunication",
stockCode: "888607",
name: 'Telecommunication',
code: '888607',
price: 1349.47, price: 1349.47,
change: "+7.67%",
isRising: true,
change: '+7.67%',
isRising: true
}, },
{ {
stockName: "Other",
stockCode: "888607",
name: 'Other',
code: '888607',
price: 1349.47, price: 1349.47,
change: "+6.67%",
isRising: true,
change: '+6.67%',
isRising: true
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1349.47, price: 1349.47,
change: "+5.67%",
isRising: true,
change: '+5.67%',
isRising: true
}, },
{ {
stockName: "Telecommunication",
stockCode: "888607",
name: 'Telecommunication',
code: '888607',
price: 1349.47, price: 1349.47,
change: "+4.67%",
isRising: true,
change: '+4.67%',
isRising: true
}, },
{ {
stockName: "Other",
stockCode: "888611",
name: 'Other',
code: '888611',
price: 1359.47, price: 1359.47,
change: "+3.67%",
isRising: true,
change: '+3.67%',
isRising: true
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1349.47, price: 1349.47,
change: "+2.67%",
isRising: true,
change: '+2.67%',
isRising: true
}, },
{ {
stockName: "Telecommunication",
stockCode: "888607",
name: 'Telecommunication',
code: '888607',
price: 1349.47, price: 1349.47,
change: "+1.67%",
isRising: true,
change: '+1.67%',
isRising: true
}, },
{ {
stockName: "Other",
stockCode: "888611",
name: 'Other',
code: '888611',
price: 1009.98, price: 1009.98,
change: "-1.67%",
isRising: false,
change: '-1.67%',
isRising: false
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1009.98, price: 1009.98,
change: "-0.67%",
isRising: false,
change: '-0.67%',
isRising: false
}, },
{ {
stockName: "Telecommunication",
stockCode: "888607",
name: 'Telecommunication',
code: '888607',
price: 1009.98, price: 1009.98,
change: "-0.67%",
isRising: false,
change: '-0.67%',
isRising: false
}, },
{ {
stockName: "Other",
stockCode: "888611",
name: 'Other',
code: '888611',
price: 1009.98, price: 1009.98,
change: "-1.67%",
isRising: false,
change: '-1.67%',
isRising: false
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1009.98, price: 1009.98,
change: "-4.67%",
isRising: false,
change: '-4.67%',
isRising: false
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1009.98, price: 1009.98,
change: "-3.67%",
isRising: false,
change: '-3.67%',
isRising: false
}, },
{ {
stockName: "Consumer Discretio...",
stockCode: "888610",
name: 'Consumer Discretio...',
code: '888610',
price: 1009.98, price: 1009.98,
change: "-3.67%",
isRising: false,
},
]);
change: '-3.67%',
isRising: false
}
])
// //
const contentTopPosition = computed(() => { const contentTopPosition = computed(() => {
return iSMT.value + headerHeight.value;
});
return iSMT.value + headerHeight.value
})
const sortedStockList = computed(() => { const sortedStockList = computed(() => {
console.log("计算sortedStockList,原始数据长度:", stockList.value.length);
let list = [...stockList.value];
console.log('计算sortedStockList,原始数据长度:', stockList.value.length);
let list = [...stockList.value]
if (sortType.value === "price") {
if (sortType.value === 'price') {
list.sort((a, b) => { list.sort((a, b) => {
return sortOrder.value === "asc" ? a.price - b.price : b.price - a.price;
});
} else if (sortType.value === "change") {
return sortOrder.value === 'asc' ? a.price - b.price : b.price - a.price
})
} else if (sortType.value === 'change') {
list.sort((a, b) => { list.sort((a, b) => {
const aChange = parseFloat(a.change.replace(/[+%-]/g, ""));
const bChange = parseFloat(b.change.replace(/[+%-]/g, ""));
return sortOrder.value === "asc" ? aChange - bChange : bChange - aChange;
});
const aChange = parseFloat(a.change.replace(/[+%-]/g, ''))
const bChange = parseFloat(b.change.replace(/[+%-]/g, ''))
return sortOrder.value === 'asc' ? aChange - bChange : bChange - aChange
})
} }
console.log("排序后数据长度:", list.length);
return list;
});
console.log('排序后数据长度:', list.length);
return list
})
// //
onLoad((options) => { onLoad((options) => {
if (options && options.market) { if (options && options.market) {
marketType.value = options.market;
marketType.value = options.market
switch (options.market) { switch (options.market) {
case "america":
marketTitle.value = "美洲";
break;
case "asia":
marketTitle.value = "亚太";
break;
case "asia-china":
marketTitle.value = "亚太-中华";
break;
case 'america':
marketTitle.value = '美洲'
break
case 'asia':
marketTitle.value = '亚太'
break
case 'asia-china':
marketTitle.value = '亚太-中华'
break
default: default:
marketTitle.value = "全球指数";
marketTitle.value = '全球指数'
} }
} }
});
})
// //
const goBack = () => { const goBack = () => {
uni.navigateBack();
};
uni.navigateBack()
}
const sortByPrice = () => { const sortByPrice = () => {
if (sortType.value === "price") {
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
if (sortType.value === 'price') {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else { } else {
sortType.value = "price";
sortOrder.value = "desc";
sortType.value = 'price'
sortOrder.value = 'desc'
} }
};
}
const sortByChange = () => { const sortByChange = () => {
if (sortType.value === "change") {
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
if (sortType.value === 'change') {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else { } else {
sortType.value = "change";
sortOrder.value = "desc";
sortType.value = 'change'
sortOrder.value = 'desc'
} }
};
}
const viewStockDetail = (stock) => { const viewStockDetail = (stock) => {
console.log("查看股票详情:", stock);
console.log('查看股票详情:', stock)
// //
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(stock))}`,
});
};
}
onMounted(() => { onMounted(() => {
// //
iSMT.value = uni.getSystemInfoSync().statusBarHeight; iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// header // header
uni
.createSelectorQuery()
.select(".header_fixed")
.boundingClientRect((rect) => {
uni.createSelectorQuery().select('.header_fixed').boundingClientRect((rect) => {
if (rect) { if (rect) {
headerHeight.value = rect.height;
console.log("Header实际高度:", headerHeight.value, "px");
headerHeight.value = rect.height
console.log('Header实际高度:', headerHeight.value, 'px')
} }
})
.exec();
});
}).exec()
})
// headerHeightcontentHeight // headerHeightcontentHeight
watch(headerHeight, (newHeight) => { watch(headerHeight, (newHeight) => {
if (newHeight > 0) { if (newHeight > 0) {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const statusBarHeight = systemInfo.statusBarHeight || 0;
const footerHeight = 100;
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const statusBarHeight = systemInfo.statusBarHeight || 0
const footerHeight = 100
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight;
console.log("重新计算contentHeight:", contentHeight.value);
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight
console.log('重新计算contentHeight:', contentHeight.value)
} }
});
})
</script> </script>
<style scoped> <style scoped>
@ -465,11 +462,11 @@ watch(headerHeight, (newHeight) => {
} }
.rising { .rising {
color: #00c851;
color: #00C851;
} }
.falling { .falling {
color: #ff4444;
color: #FF4444;
} }
/* 底部安全区域 */ /* 底部安全区域 */

733
pages/home/marketOverview.vue

@ -0,0 +1,733 @@
<template>
<view class="main">
<!-- 可滚动内容区域 -->
<scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }">
<view class="content">
<button @click="goToChartExample">图表</button>
<view class="map">
<image src="/static/marketSituation-image/map.png" mode="widthFix"></image>
</view>
<view class="global_index">
<view class="global_index_title">
{{ $t('marketSituation.globalIndex') }}
</view>
<view class="global_index_more" @click="goToGlobalIndex">
<text>{{ $t('marketSituation.globalIndexMore') }}</text>
<image src="/static/marketSituation-image/more.png" mode="aspectFit"></image>
</view>
</view>
<!-- 卡片网格 -->
<view class="cards_grid">
<view v-for="(card, index) in cardData" :key="index" class="card_item">
<IndexCard :flagIcon="card.flagIcon" :indexName="card.indexName"
:currentPrice="card.currentPrice" :changeAmount="card.changeAmount"
:changePercent="card.changePercent" :isRising="card.isRising" />
</view>
</view>
<view class="warn">
<image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
<view class="warn_text_container">
<text :class="warnTextClass">{{ $t('marketSituation.warn') }}</text>
</view>
</view>
<!-- 底部安全区域防止被导航栏遮挡 -->
<view class="bottom_safe_area"></view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import util from '../../common/util.js'
import IndexCard from '../../components/IndexCard.vue'
const iSMT = ref(0)
const searchValue = ref('')
const contentHeight = ref(0)
const headerHeight = ref(0) // header
const isWarnTextOverflow = ref(false) // warn
const pageIndex = ref(0)
const scrollToView = ref('')
//
const goToChartExample = () => {
uni.navigateTo({
url: '/pages/marketSituation/chartExample'
})
}
// contenttop
const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140
return statusBarHeight + currentHeaderHeight
})
// warnclass
const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? 'warn_text scroll-active' : 'warn_text'
})
//
const showCountryModal = ref(false)
const selectedCountry = ref('概况')
const countryList = ref([
'概况', '新加坡', '马来西亚', '印度尼西亚', '美国', '中国香港',
'泰国', '中国', '加拿大', '越南', '外汇', '贵金属'
])
//
const cardData = ref([
{
flagIcon: '🇺🇸',
indexName: '道琼斯',
currentPrice: '45757.90',
changeAmount: '-125.22',
changePercent: '-0.27%',
isRising: false
},
{
flagIcon: '🇺🇸',
indexName: '纳斯达克',
currentPrice: '22333.96',
changeAmount: '+125.22',
changePercent: '+0.47%',
isRising: true
},
{
flagIcon: '🇺🇸',
indexName: '标普500',
currentPrice: '6606.08',
changeAmount: '+125.22',
changePercent: '+0.27%',
isRising: true
},
{
flagIcon: '🇨🇳',
indexName: '上证指数',
currentPrice: '3333.96',
changeAmount: '+125.22',
changePercent: '+0.27%',
isRising: true
},
{
flagIcon: '🇨🇳',
indexName: '科创50',
currentPrice: '757.90',
changeAmount: '-25.22',
changePercent: '-0.27%',
isRising: false
},
{
flagIcon: '🇭🇰',
indexName: '恒生指数',
currentPrice: '19757.90',
changeAmount: '-125.22',
changePercent: '-0.63%',
isRising: false
},
{
flagIcon: '🇸🇬',
indexName: '道琼斯',
currentPrice: '3757.90',
changeAmount: '+85.22',
changePercent: '+2.31%',
isRising: true
},
{
flagIcon: '🇲🇾',
indexName: '纳斯达克',
currentPrice: '1657.90',
changeAmount: '-15.22',
changePercent: '-0.91%',
isRising: false
},
{
flagIcon: '🇹🇭',
indexName: '标普500',
currentPrice: '1457.90',
changeAmount: '+35.22',
changePercent: '+2.48%',
isRising: true
}
])
//
const onSearchInput = (e) => {
searchValue.value = e.detail.value
}
//
const onSearchConfirm = (e) => {
console.log('搜索内容:', e.detail.value)
//
performSearch(e.detail.value)
}
//
const onSearchClick = () => {
if (searchValue.value.trim()) {
performSearch(searchValue.value)
}
}
//
const performSearch = (keyword) => {
if (!keyword.trim()) {
uni.showToast({
title: '请输入搜索内容',
icon: 'none'
})
return
}
uni.showToast({
title: `搜索: ${keyword}`,
icon: 'none'
})
//
}
// warn
const checkWarnTextOverflow = () => {
nextTick(() => {
setTimeout(() => {
const query = uni.createSelectorQuery()
//
query.select('.warn_text_container').boundingClientRect()
query.select('.warn_text').boundingClientRect()
query.exec((res) => {
const containerRect = res[0]
const textRect = res[1]
if (!containerRect || !textRect) {
return
}
//
const isOverflow = textRect.width > (containerRect.width - 10)
isWarnTextOverflow.value = isOverflow
})
}, 500)
})
}
//
const goToGlobalIndex = () => {
uni.navigateTo({
url: '/pages/marketSituation/globalIndex'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// DOM
nextTick(() => {
// header
uni.createSelectorQuery().select('.header_fixed').boundingClientRect((rect) => {
if (rect) {
headerHeight.value = rect.height
console.log('Header实际高度:', headerHeight.value, 'px')
}
}).exec()
// warn
checkWarnTextOverflow()
})
})
// headerHeightcontentHeight
watch(headerHeight, (newHeight) => {
if (newHeight > 0) {
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const statusBarHeight = systemInfo.statusBarHeight || 0
const footerHeight = 100
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight
console.log('重新计算contentHeight:', contentHeight.value)
}
})
</script>
<style scoped>
/* 状态栏占位 */
.top {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1001;
background-color: #ffffff;
}
/* 固定头部样式 */
.header_fixed {
position: fixed;
left: 0;
right: 0;
z-index: 1000;
background-color: #ffffff;
padding: 20rpx 0 0 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
/* 可滚动内容区域 */
.content_scroll {
position: fixed;
left: 0;
right: 0;
bottom: 100rpx;
/* 底部导航栏高度 */
overflow-y: auto;
}
.header_content {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 20rpx;
margin-bottom: 10rpx;
}
.header_input_wrapper {
display: flex;
align-items: center;
width: 100%;
margin: 0 20rpx 0 0;
height: 70rpx;
border-radius: 35rpx;
background-color: #ffffff;
border: 1rpx solid #e9ecef;
padding: 0 80rpx 0 30rpx;
font-size: 28rpx;
color: #5c5c5c;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.search_icon {
width: 40rpx;
height: 40rpx;
opacity: 0.6;
}
.header_input {
margin-left: 10rpx;
}
.header_icons {
display: flex;
align-items: center;
gap: 15rpx;
}
.header_icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header_icon image {
width: 40rpx;
height: 40rpx;
}
/* Tab 栏样式 */
.channel_li {
display: flex;
align-items: center;
height: 80rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.channel_wrap {
width: calc(100% - 60rpx);
height: 100%;
overflow: hidden;
flex-shrink: 0;
}
.channel_innerWrap {
display: flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
white-space: nowrap;
}
.channel_item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60rpx;
padding: 0 20rpx;
border-radius: 30rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
}
.channel_item:active {
transform: scale(0.98);
}
.channel_item.active {
color: #333;
font-weight: bold;
}
.channel_text {
font-size: 28rpx;
font-weight: 500;
color: #666666;
transition: color 0.3s ease;
white-space: nowrap;
}
.channel_item.active .channel_text {
color: #333333;
font-weight: 400;
z-index: 2;
}
.active_indicator {
position: absolute;
left: 50%;
top: 60%;
transform: translateX(-45%);
width: calc(100% - 20rpx);
min-width: 40rpx;
max-width: 120rpx;
height: 8rpx;
background-image: url('/static/marketSituation-image/bg.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
animation: slideIn 0.1s ease;
border-radius: 8rpx;
z-index: 1;
}
@keyframes slideIn {
from {
width: 0;
opacity: 0;
}
to {
width: 40rpx;
opacity: 1;
}
}
.scroll_indicator {
border-left: 1rpx solid #b6b6b6;
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 30rpx;
background-color: #ffffff;
flex-shrink: 0;
}
.scroll_indicator image {
width: 20rpx;
height: 20rpx;
opacity: 0.5;
}
.content {
margin-top: 20rpx;
background-color: white;
}
.map {
width: calc(100% - 60rpx);
margin: 0 30rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #F3F3F3;
border-radius: 30rpx;
border: 1rpx solid #E0E0E0;
padding: 30rpx 20rpx;
box-sizing: border-box;
/* 设置最小高度保护,但允许内容撑开 */
min-height: 200rpx;
}
.map image {
width: 100%;
height: auto;
max-width: 100%;
display: block;
/* widthFix模式下,高度会自动按比例调整 */
/* 设置最大高度避免图片过大 */
max-height: 60vh;
/* 添加平滑过渡效果 */
transition: all 0.3s ease;
max-height: 60vh;
}
/* 响应式优化 */
@media screen and (max-width: 750rpx) {
.map {
margin: 0 20rpx;
width: calc(100% - 40rpx);
padding: 20rpx 15rpx;
}
}
@media screen and (max-width: 480rpx) {
.map {
margin: 0 15rpx;
width: calc(100% - 30rpx);
padding: 15rpx 10rpx;
}
}
.static-footer {
position: fixed;
bottom: 0;
}
/* 弹窗样式 */
.modal_overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.modal_content {
width: 100%;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
max-height: 80vh;
overflow: hidden;
}
.modal_header {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.modal_title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
text-align: center;
}
.modal_close {
position: absolute;
right: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #999;
}
.modal_body {
padding: 40rpx;
}
.country_grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.country_item {
padding: 24rpx 30rpx;
border-radius: 12rpx;
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.country_item.selected {
background-color: #ff4444;
color: #fff;
}
.country_text {
font-size: 28rpx;
color: #333;
}
.country_item.selected .country_text {
color: #fff;
}
.global_index {
margin: 30rpx 20rpx 0 20rpx;
display: flex;
justify-content: space-between;
}
.global_index_title {
margin-left: 20rpx;
font-size: 40rpx;
font-weight: 100;
color: #333333;
align-items: center;
}
.global_index_more {
display: flex;
gap: 10rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
}
.global_index_more image {
width: 40rpx;
height: 40rpx;
align-items: center;
}
/* 卡片网格样式 */
.cards_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 30rpx 20rpx;
gap: 20rpx;
}
.card_item {
width: 100%;
box-sizing: border-box;
min-width: 0;
/* 防止内容溢出 */
}
/* 响应式布局 - 小屏幕时改为两列 */
@media (max-width: 600rpx) {
.cards_grid {
grid-template-columns: repeat(2, 1fr);
padding: 30rpx 20rpx;
}
}
/* 超小屏幕时改为单列 */
@media (max-width: 400rpx) {
.cards_grid {
grid-template-columns: 1fr;
padding: 30rpx 20rpx;
}
}
.warn {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10rpx;
font-size: 28rpx;
color: #666666;
padding: 20rpx;
max-width: 100%;
overflow: hidden;
position: relative;
}
.warn image {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
/* 防止图片被压缩 */
position: relative;
z-index: 2;
/* 确保图片在最上层 */
}
.warn_text_container {
flex: 1;
overflow: hidden;
position: relative;
min-width: 0;
/* 允许容器收缩 */
}
.warn_text {
display: block;
white-space: nowrap;
will-change: transform;
/* 优化动画性能 */
}
/* 文字滚动动画 */
@keyframes scrollText {
0% {
transform: translateX(0);
}
20% {
transform: translateX(0);
}
80% {
transform: translateX(-85%);
}
100% {
transform: translateX(-85%);
}
}
/* 当文字超长时启用滚动动画 */
.warn_text.scroll-active {
animation: scrollText 12s linear infinite;
animation-delay: 2s;
/* 延迟2秒开始滚动,让用户先看到开头 */
}
/* 底部安全区域 */
.bottom_safe_area {
height: 40rpx;
background-color: transparent;
}
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
</style>

629
pages/home/marketSituation.vue

@ -1,13 +1,15 @@
<!-- @format -->
<template> <template>
<view>
<view class="main"> <view class="main">
<!-- 固定头部 --> <!-- 固定头部 -->
<view class="header_fixed" :style="{ top: iSMT + 'px' }"> <view class="header_fixed" :style="{ top: iSMT + 'px' }">
<view class="header_content"> <view class="header_content">
<view class="header_input_wrapper"> <view class="header_input_wrapper">
<image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" />
<image class="search_icon" src="/static/marketSituation-image/search.png" mode=""
@click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索"
placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue"
@input="onSearchInput" @confirm="onSearchConfirm" />
</view> </view>
<view class="header_icons"> <view class="header_icons">
<view class="header_icon" @click="selected"> <view class="header_icon" @click="selected">
@ -19,9 +21,11 @@
</view> </view>
</view> </view>
<view class="channel_li" v-if="channelData.length > 0"> <view class="channel_li" v-if="channelData.length > 0">
<scroll-view class="channel_wrap" scroll-x="true" :scroll-into-view="scrollToView" :scroll-with-animation="true" show-scrollbar="false">
<scroll-view class="channel_wrap" scroll-x="true" :scroll-into-view="scrollToView"
:scroll-with-animation="true" show-scrollbar="false">
<view class="channel_innerWrap"> <view class="channel_innerWrap">
<view v-for="(item, index) in channelData" :key="item.id" :id="'nav' + item.id" :class="['channel_item', index === pageIndex ? 'active' : '']" @click="navClick(index)">
<view v-for="(item, index) in channelData" :key="item.id" :id="'nav' + item.id"
:class="['channel_item', index === pageIndex ? 'active' : '']" @click="navClick(index)">
<text class="channel_text">{{ item.title }}</text> <text class="channel_text">{{ item.title }}</text>
<view v-if="index === pageIndex" class="active_indicator"></view> <view v-if="index === pageIndex" class="active_indicator"></view>
</view> </view>
@ -35,35 +39,8 @@
<!-- 可滚动内容区域 --> <!-- 可滚动内容区域 -->
<scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }"> <scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }">
<view class="content">
<view class="map">
<image src="/static/marketSituation-image/map.png" mode="widthFix"></image>
</view>
<view class="global_index">
<view class="global_index_title">
{{ $t("marketSituation.globalIndex") }}
</view>
<view class="global_index_more" @click="goToGlobalIndex">
<text>{{ $t("marketSituation.globalIndexMore") }}</text>
<image src="/static/marketSituation-image/more.png" mode="aspectFit"></image>
</view>
</view>
<!-- 卡片网格 -->
<view class="cards_grid">
<view v-for="(card, index) in cardData" :key="index" class="card_item">
<IndexCard :flagIcon="card.flagIcon" :stockName="card.stockName" :currentPrice="card.currentPrice" :changeAmount="card.changeAmount" :changePercent="card.changePercent" :isRising="card.isRising" @click="viewIndexDetail(card)" />
</view>
</view>
<view class="warn">
<image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
<view class="warn_text_container">
<text :class="warnTextClass">{{ $t("marketSituation.warn") }}</text>
</view>
</view>
<!-- 底部安全区域防止被导航栏遮挡 -->
<view class="bottom_safe_area"></view>
</view>
<!-- 动态组件切换 -->
<component :is="currentComponent" :countryId="currentChannelId" />
</scroll-view> </scroll-view>
</view> </view>
@ -80,315 +57,206 @@
</view> </view>
<view class="modal_body"> <view class="modal_body">
<view class="country_grid"> <view class="country_grid">
<view v-for="(country, index) in countryList" :key="index" :class="['country_item', selectedCountry === country ? 'selected' : '']" @click="selectCountry(country)">
<view v-for="(country, index) in countryList" :key="index"
:class="['country_item', selectedCountry === country ? 'selected' : '']"
@click="selectCountry(country)">
<text class="country_text">{{ country }}</text> <text class="country_text">{{ country }}</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, nextTick, computed } from "vue";
import util from "../../common/util.js";
import footerBar from "../../components/footerBar.vue";
import IndexCard from "../../components/IndexCard.vue";
const type = ref("marketSituation");
const iSMT = ref(0);
const searchValue = ref("");
const contentHeight = ref(0);
const headerHeight = ref(0); // header
const isWarnTextOverflow = ref(false); // warn
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import footerBar from './footerBar.vue'
import forexMetals from './forexMetals.vue'
import marketOverview from './marketOverview.vue'
import countryMarket from './countryMarket.vue'
const type = ref('marketSituation')
const iSMT = ref(0)
const searchValue = ref('')
const contentHeight = ref(0)
const headerHeight = ref(0) // header
// Tab // Tab
const channelData = ref([ const channelData = ref([
{ id: 1, title: "概况" },
{ id: 2, title: "新加坡" },
{ id: 3, title: "马来西亚" },
{ id: 4, title: "印度尼西亚" },
{ id: 5, title: "美国" },
{ id: 6, title: "中国香港" },
{ id: 7, title: "泰国" },
{ id: 8, title: "中国" },
{ id: 9, title: "加拿大" },
{ id: 10, title: "越南" },
{ id: 11, title: "外汇" },
{ id: 12, title: "贵金属" },
]);
const pageIndex = ref(0);
const scrollToView = ref("");
{ id: 1, title: '概况' },
{ id: 2, title: '新加坡' },
{ id: 3, title: '马来西亚' },
{ id: 4, title: '印度尼西亚' },
{ id: 5, title: '美国' },
{ id: 6, title: '中国香港' },
{ id: 7, title: '泰国' },
{ id: 8, title: '中国' },
{ id: 9, title: '加拿大' },
{ id: 10, title: '越南' },
{ id: 11, title: '外汇' },
{ id: 12, title: '贵金属' },
])
const pageIndex = ref(0)
const scrollToView = ref('')
//
const currentChannelId = computed(() => {
return channelData.value[pageIndex.value]?.id || 1
})
const currentComponent = computed(() => {
const channelId = currentChannelId.value
// 使 MarketOverview
if (pageIndex.value === 0) {
return marketOverview
}
// (id=11)(id=12)使 ForexMetals
else if (channelId === 11 || channelId === 12) {
return forexMetals
}
// /使 CountryMarket
else {
return countryMarket
}
})
// contenttop // contenttop
const contentTopPosition = computed(() => { const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0;
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140;
return statusBarHeight + currentHeaderHeight;
});
// warnclass
const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? "warn_text scroll-active" : "warn_text";
});
const statusBarHeight = iSMT.value || 0
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140
return statusBarHeight + currentHeaderHeight
})
// //
const showCountryModal = ref(false);
const selectedCountry = ref("概况");
const countryList = ref(["概况", "新加坡", "马来西亚", "印度尼西亚", "美国", "中国香港", "泰国", "中国", "加拿大", "越南", "外汇", "贵金属"]);
//
const cardData = ref([
{
flagIcon: "🇺🇸",
stockName: "道琼斯",
stockCode: "DJIA",
currentPrice: "45757.90",
changeAmount: "-125.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇺🇸",
stockName: "纳斯达克",
stockCode: "NDX",
currentPrice: "22333.96",
changeAmount: "+125.22",
changePercent: "+0.47%",
isRising: true,
},
{
flagIcon: "🇺🇸",
stockName: "标普500",
stockCode: "SPX",
currentPrice: "6606.08",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "上证指数",
stockCode: "1A0001",
currentPrice: "3333.96",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "科创50",
stockCode: "1B0688",
currentPrice: "757.90",
changeAmount: "-25.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇭🇰",
stockName: "恒生指数",
stockCode: "HSI",
currentPrice: "19757.90",
changeAmount: "-125.22",
changePercent: "-0.63%",
isRising: false,
},
{
flagIcon: "🇸🇬",
stockName: "道琼斯",
stockCode: "DJIA",
currentPrice: "3757.90",
changeAmount: "+85.22",
changePercent: "+2.31%",
isRising: true,
},
{
flagIcon: "🇲🇾",
stockName: "纳斯达克",
stockCode: "NDX",
currentPrice: "1657.90",
changeAmount: "-15.22",
changePercent: "-0.91%",
isRising: false,
},
{
flagIcon: "🇹🇭",
stockName: "标普500",
stockCode: "SPX",
currentPrice: "1457.90",
changeAmount: "+35.22",
changePercent: "+2.48%",
isRising: true,
},
]);
const showCountryModal = ref(false)
const selectedCountry = ref('概况')
const countryList = ref([
'概况', '新加坡', '马来西亚', '印度尼西亚', '美国', '中国香港',
'泰国', '中国', '加拿大', '越南', '外汇', '贵金属'
])
// //
const onSearchInput = (e) => { const onSearchInput = (e) => {
searchValue.value = e.detail.value;
};
searchValue.value = e.detail.value
}
// //
const onSearchConfirm = (e) => { const onSearchConfirm = (e) => {
console.log("搜索内容:", e.detail.value);
console.log('搜索内容:', e.detail.value)
// //
performSearch(e.detail.value);
};
performSearch(e.detail.value)
}
// //
const onSearchClick = () => { const onSearchClick = () => {
if (searchValue.value.trim()) { if (searchValue.value.trim()) {
performSearch(searchValue.value);
performSearch(searchValue.value)
} }
};
}
// //
const performSearch = (keyword) => { const performSearch = (keyword) => {
if (!keyword.trim()) { if (!keyword.trim()) {
uni.showToast({ uni.showToast({
title: "请输入搜索内容",
icon: "none",
});
return;
title: '请输入搜索内容',
icon: 'none'
})
return
} }
uni.showToast({ uni.showToast({
title: `搜索: ${keyword}`, title: `搜索: ${keyword}`,
icon: "none",
});
icon: 'none'
})
// //
};
}
// //
const selected = () => { const selected = () => {
uni.showToast({ uni.showToast({
title: "我的收藏",
icon: "none",
});
title: '我的收藏',
icon: 'none'
})
// //
};
}
// //
const history = () => { const history = () => {
uni.showToast({ uni.showToast({
title: "历史记录",
icon: "none",
});
title: '历史记录',
icon: 'none'
})
// //
};
}
// Tab // Tab
const navClick = (index) => { const navClick = (index) => {
pageIndex.value = index;
const currentItem = channelData.value[index];
scrollToView.value = "nav" + currentItem.id;
pageIndex.value = index
const currentItem = channelData.value[index]
scrollToView.value = 'nav' + currentItem.id
// //
selectedCountry.value = currentItem.title;
selectedCountry.value = currentItem.title
uni.showToast({ uni.showToast({
title: `切换到: ${currentItem.title}`, title: `切换到: ${currentItem.title}`,
icon: "none",
});
icon: 'none'
})
// tab // tab
console.log("当前选中的 tab:", currentItem);
};
console.log('当前选中的 tab:', currentItem)
}
// //
const channel_more = () => { const channel_more = () => {
showCountryModal.value = true;
};
showCountryModal.value = true
}
// //
const selectCountry = (country) => { const selectCountry = (country) => {
selectedCountry.value = country;
selectedCountry.value = country
// tab // tab
const targetIndex = channelData.value.findIndex((item) => item.title === country);
const targetIndex = channelData.value.findIndex(item => item.title === country)
if (targetIndex !== -1) { if (targetIndex !== -1) {
// tab // tab
pageIndex.value = targetIndex;
const currentItem = channelData.value[targetIndex];
scrollToView.value = "nav" + currentItem.id;
pageIndex.value = targetIndex
const currentItem = channelData.value[targetIndex]
scrollToView.value = 'nav' + currentItem.id
console.log("选中了:" + country + ",同步到tab索引:" + targetIndex);
console.log('选中了:' + country + ',同步到tab索引:' + targetIndex)
uni.showToast({ uni.showToast({
title: "已切换到:" + country,
icon: "none",
duration: 2000,
});
title: '已切换到:' + country,
icon: 'none',
duration: 2000
})
} else { } else {
// ""tab // ""tab
if (country === "概况" || country === "全部") {
pageIndex.value = 0;
scrollToView.value = "nav" + channelData.value[0].id;
if (country === '概况' || country === '全部') {
pageIndex.value = 0
scrollToView.value = 'nav' + channelData.value[0].id
} }
console.log("选中了:" + country);
console.log('选中了:' + country)
uni.showToast({ uni.showToast({
title: "已选择:" + country,
icon: "none",
duration: 2000,
});
title: '已选择:' + country,
icon: 'none',
duration: 2000
})
} }
// / // /
// loadMarketData(country) // loadMarketData(country)
closeModal();
};
closeModal()
}
// //
const closeModal = () => { const closeModal = () => {
showCountryModal.value = false;
};
// warn
const checkWarnTextOverflow = () => {
nextTick(() => {
setTimeout(() => {
const query = uni.createSelectorQuery();
//
query.select(".warn_text_container").boundingClientRect();
query.select(".warn_text").boundingClientRect();
query.exec((res) => {
const containerRect = res[0];
const textRect = res[1];
if (!containerRect || !textRect) {
return;
}
//
const isOverflow = textRect.width > containerRect.width - 10;
isWarnTextOverflow.value = isOverflow;
});
}, 500);
});
};
//
const goToGlobalIndex = () => {
uni.navigateTo({
url: "/pages/home/globalIndex",
});
};
//
const viewIndexDetail = (item) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
});
};
showCountryModal.value = false
}
onMounted(() => { onMounted(() => {
// //
@ -396,57 +264,45 @@ onMounted(() => {
// tab // tab
if (channelData.value.length > 0) { if (channelData.value.length > 0) {
pageIndex.value = 0;
scrollToView.value = "nav" + channelData.value[0].id;
}
util.request(
"link/api/brain/privilege",
(res) => {
console.log(res);
},
{
token: "9ior41AF0xTIbIG2pRnnbZi0+fEeMx8pywnIlrmTwo5FbqJ9lWrSWOxp9MkpKiNtedtUafqvzIwpFKrwuMs",
},
(err) => {
console.log(err);
pageIndex.value = 0
scrollToView.value = 'nav' + channelData.value[0].id
} }
);
// DOM // DOM
nextTick(() => { nextTick(() => {
// header // header
uni
.createSelectorQuery()
.select(".header_fixed")
.boundingClientRect((rect) => {
uni.createSelectorQuery().select('.header_fixed').boundingClientRect((rect) => {
if (rect) { if (rect) {
headerHeight.value = rect.height;
console.log("Header实际高度:", headerHeight.value, "px");
headerHeight.value = rect.height
console.log('Header实际高度:', headerHeight.value, 'px')
} }
}).exec()
}) })
.exec();
// warn
checkWarnTextOverflow();
});
});
})
// headerHeightcontentHeight // headerHeightcontentHeight
watch(headerHeight, (newHeight) => { watch(headerHeight, (newHeight) => {
if (newHeight > 0) { if (newHeight > 0) {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const statusBarHeight = systemInfo.statusBarHeight || 0;
const footerHeight = 100;
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const statusBarHeight = systemInfo.statusBarHeight || 0
const footerHeight = 100
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight;
console.log("重新计算contentHeight:", contentHeight.value);
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight
console.log('重新计算contentHeight:', contentHeight.value)
} }
});
})
</script> </script>
<style scoped> <style scoped>
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
/* 状态栏占位 */ /* 状态栏占位 */
.top { .top {
position: fixed; position: fixed;
@ -602,7 +458,7 @@ watch(headerHeight, (newHeight) => {
min-width: 40rpx; min-width: 40rpx;
max-width: 120rpx; max-width: 120rpx;
height: 8rpx; height: 8rpx;
background-image: url("/static/marketSituation-image/bg.png");
background-image: url('/static/marketSituation-image/bg.png');
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -645,51 +501,6 @@ watch(headerHeight, (newHeight) => {
background-color: white; background-color: white;
} }
.map {
width: calc(100% - 60rpx);
margin: 0 30rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f3f3f3;
border-radius: 30rpx;
border: 1rpx solid #e0e0e0;
padding: 30rpx 20rpx;
box-sizing: border-box;
/* 设置最小高度保护,但允许内容撑开 */
min-height: 200rpx;
}
.map image {
width: 100%;
height: auto;
max-width: 100%;
display: block;
/* widthFix模式下,高度会自动按比例调整 */
/* 设置最大高度避免图片过大 */
max-height: 60vh;
/* 添加平滑过渡效果 */
transition: all 0.3s ease;
max-height: 60vh;
}
/* 响应式优化 */
@media screen and (max-width: 750rpx) {
.map {
margin: 0 20rpx;
width: calc(100% - 40rpx);
padding: 20rpx 15rpx;
}
}
@media screen and (max-width: 480rpx) {
.map {
margin: 0 15rpx;
width: calc(100% - 30rpx);
padding: 15rpx 10rpx;
}
}
.static-footer { .static-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
@ -779,142 +590,4 @@ watch(headerHeight, (newHeight) => {
.country_item.selected .country_text { .country_item.selected .country_text {
color: #fff; color: #fff;
} }
.global_index {
margin: 30rpx 20rpx 0 20rpx;
display: flex;
justify-content: space-between;
}
.global_index_title {
margin-left: 20rpx;
font-size: 40rpx;
font-weight: 100;
color: #333333;
align-items: center;
}
.global_index_more {
display: flex;
gap: 10rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
}
.global_index_more image {
width: 40rpx;
height: 40rpx;
align-items: center;
}
/* 卡片网格样式 */
.cards_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0;
box-sizing: border-box;
width: 100%;
}
.card_item {
width: 100%;
box-sizing: border-box;
min-width: 0;
/* 防止内容溢出 */
}
/* 响应式布局 - 小屏幕时改为两列 */
@media (max-width: 600rpx) {
.cards_grid {
grid-template-columns: repeat(2, 1fr);
padding: 30rpx 20rpx;
}
}
/* 超小屏幕时改为单列 */
@media (max-width: 400rpx) {
.cards_grid {
grid-template-columns: 1fr;
padding: 30rpx 20rpx;
}
}
.warn {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10rpx;
font-size: 28rpx;
color: #666666;
padding: 20rpx;
max-width: 100%;
overflow: hidden;
position: relative;
}
.warn image {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
/* 防止图片被压缩 */
position: relative;
z-index: 2;
/* 确保图片在最上层 */
}
.warn_text_container {
flex: 1;
overflow: hidden;
position: relative;
min-width: 0;
/* 允许容器收缩 */
}
.warn_text {
display: block;
white-space: nowrap;
will-change: transform;
/* 优化动画性能 */
}
/* 文字滚动动画 */
@keyframes scrollText {
0% {
transform: translateX(0);
}
20% {
transform: translateX(0);
}
80% {
transform: translateX(-85%);
}
100% {
transform: translateX(-85%);
}
}
/* 当文字超长时启用滚动动画 */
.warn_text.scroll-active {
animation: scrollText 12s linear infinite;
animation-delay: 2s;
/* 延迟2秒开始滚动,让用户先看到开头 */
}
/* 底部安全区域 */
.bottom_safe_area {
height: 40rpx;
background-color: transparent;
}
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
</style> </style>

BIN
static/marketSituation-image/cool.png

After

Width: 50  |  Height: 110  |  Size: 11 KiB

BIN
static/marketSituation-image/hot.png

After

Width: 50  |  Height: 109  |  Size: 11 KiB

BIN
static/marketSituation-image/warm.png

After

Width: 50  |  Height: 110  |  Size: 12 KiB

Loading…
Cancel
Save