|
|
<template> <view class="main"> <!-- 顶部状态栏占位 --> <view class="top" :style="{height:iSMT+'px'}"></view> <!-- 头部导航 --> <view class="header"> <view class="headphone-icon"> <image src="https://d31zlh4on95l9h.cloudfront.net/images/bef2edba6cc0c85671fde07cfab5270d.png" class="header-icon-image"></image> </view> <view class="title">DeepChart</view> <view class="notification-icon"> <image src="https://d31zlh4on95l9h.cloudfront.net/images/2554c84b91712d2a6cb6b00380e63bac.png" class="header-icon-image"></image> </view> </view> <!-- 内容区域 - 使用滚动视图 --> <scroll-view scroll-y class="content-container"> <!-- 1. 今日市场概览 --> <market-overview :stock-info-list="currentStockInfoList"></market-overview> <!-- 间隔 --> <view class="section-gap"></view> <!-- 新增欢迎部分 --> <view class="section welcome-section"> <!-- 轮播图 --> <swiper class="welcome-swiper" circular autoplay interval="3000" duration="500" indicator-dots indicator-active-color="#4080ff"> <swiper-item v-for="(item, index) in 5" :key="index"> <image class="swiper-image" src="https://d31zlh4on95l9h.cloudfront.net/images/e4272cc034fa2a3d1ca588ef84e51ab0.png" mode="aspectFill"></image> </swiper-item> </swiper> </view> <!-- 2. DeepMate --> <view class="section deepmate-section"> <DeepMate /> </view> <!-- 3. 深度探索 --> <view class="section deep-exploration"> <!-- 上部分:标题和查看更多按钮 --> <view class="section-header-container"> <view class="section-header"> <view class="header-left"> <text class="section-title">深度探索</text> </view> <view class="header-right"> <text class="more-btn" @click="goToDeepExploration">查看更多</text> </view> </view> </view> <!-- 下部分:四个图标 --> <view class="exploration-container"> <view class="exploration-content"> <view class="exploration-item"> <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/199472b0ee90a1c897f7c87b85accd84.png" mode="aspectFit" lazy-load="true"></image> <text class="exploration-text">主力追踪</text> </view> <view class="exploration-item"> <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/c25ca5e176efc961dabfa5d0d1b486b0.png" mode="aspectFit" lazy-load="true"></image> <text class="exploration-text">主力资达</text> </view> <view class="exploration-item"> <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/c064d7066dc8129a7df7b052762f82cf.png" mode="aspectFit" lazy-load="true"></image> <text class="exploration-text">主力解码</text> </view> <view class="exploration-item"> <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/9d69cceee9c515911477078af6f68d88.png" mode="aspectFit" lazy-load="true"></image> <text class="exploration-text">主力资金流</text> </view> </view> </view> </view> <!-- 4. 我的自选 --> <view class="section my-selection"> <!-- 上部分:标题和查看更多按钮 --> <view class="section-header-container"> <view class="section-header"> <text class="section-title">我的自选</text> <text class="more-btn" @click="goToMarketSituation">添加自选股</text> </view> </view> <!-- 下部分:股票列表 --> <view class="stock-container"> <view class="stock-list"> <view class="stock-item" v-for="(item, index) in myStocks" :key="item.code"> <view class="stock-info"> <view class="name-container"> <text class="stock-name">{{item.name}}</text> <text class="stock-code-label">{{item.code}}</text> </view> <view class="price-container"> <text class="stock-price">{{item.price}}</text> <text class="stock-change" :class="{'stock-up': item.change > 0, 'stock-down': item.change < 0}">{{item.change > 0 ? '+' : ''}}{{item.change}}%</text> </view> </view> <view class="stock-chart"> <image :src="item.chartImg" mode="aspectFit" class="chart-image"></image> </view> </view> </view> <!-- 机构动向简报数据 --> <view class="institutional-reports"> <view class="section-title-container"> <text class="section-title-text">机构动向简报</text> </view> <view class="text-gap"></view> <view class="report-item" v-for="(report, index) in institutionalReports" :key="index"> <view class="report-stock">{{report.stock}}</view> <view class="report-status">{{report.status}}</view> </view> <view class="view-more"> <view><text>查看更多 >></text></view> <view><text class="disclaimer-text">免责声明:以上数据由AI生成,不作为最终投资建议,决策需独立!</text></view> </view> </view> </view> </view> <!-- 5. TCP连接测试 --> <!-- <view class="section tcp-test-section"> <view class="section-header"> <text class="section-title">TCP连接测试</text> <view class="tcp-status" :class="{'connected': tcpConnected, 'disconnected': !tcpConnected}"> <text class="status-text">{{tcpConnected ? '已连接' : '未连接'}}</text> </view> </view> --> <!-- TCP控制按钮 --> <!-- <view class="tcp-controls"> <button class="tcp-btn connect-btn" @click="connectTcp" :disabled="tcpConnected">连接</button> <button class="tcp-btn disconnect-btn" @click="disconnectTcp" :disabled="!tcpConnected">断开</button> <button class="tcp-btn send-btn" @click="sendTcpMessage" :disabled="!tcpConnected">发送测试消息</button> <button class="tcp-btn status-btn" @click="getTcpStatus">查看状态</button> </view> --> <!-- 消息记录 --> <!-- <view class="tcp-messages" v-if="tcpMessages.length > 0"> <view class="messages-header"> <text class="messages-title">消息记录 ({{tcpMessages.length}})</text> <button class="clear-btn" @click="clearTcpMessages">清空</button> </view> <scroll-view scroll-y class="messages-list" scroll-top="{{tcpMessages.length * 50}}"> <view class="message-item" v-for="(msg, index) in tcpMessages" :key="index" :class="{'sent': msg.direction === 'sent', 'received': msg.direction === 'received'}"> <view class="message-info"> <text class="message-direction">{{msg.direction === 'sent' ? '发送' : '接收'}}</text> <text class="message-time">{{msg.timestamp}}</text> </view> <text class="message-content">{{msg.content}}</text> </view> </scroll-view> </view> </view> --> <!-- 6. 今日市场看点 --> <view class="section-header highlights-title-container"> <text class="section-title">今日市场核心看点</text> </view> <view class="highlights-image-container"> <image src="https://d31zlh4on95l9h.cloudfront.net/images/8d5365af968402a18cedb120c09460b0.png" mode="aspectFit" class="highlights-image"></image> </view> <!-- 底部空间 - 为底部导航腾出空间 --> <view class="bottom-space"></view> </scroll-view> <!-- 底部导航 --> <footerBar class="static-footer" :type="type"></footerBar> </view></template>
<script>import footerBar from '../../components/footerBar.vue'import MarketOverview from '../../components/MarketOverview.vue'import DeepMate from '../../components/DeepMate.vue'import tcpConnection from '../../api/tcpConnection.js'import th from '../../static/language/th'
export default { components: { footerBar, MarketOverview, DeepMate }, data() { return { type: 'home', iSMT: 0, // 深度探索数据
explorationItems: [ { title: '主力追踪', icon: '/static/c1.png' }, { title: '主力资金', icon: '/static/c2.png' }, { title: '主力解码', icon: '/static/c3.png' }, { title: '主力资金流', icon: '/static/c4.png' } ], // 我的自选股票数据
myStocks: [ { name: '特斯拉', code: 'TSLA', price: '482.00', change: 2.80, chartImg: '/static/c5.png' }, { name: '英伟达', code: 'NVDA', price: '189.800', change: -2.92, chartImg: '/static/c6.png' }, { name: '苹果', code: 'AAPL', price: '256.430', change: 2.60, chartImg: '/static/c7.png' } ], // 机构动向简报数据
institutionalReports: [ { stock: '特斯拉', status: '当前市场多头资金占比,且多头资金持续流入。' }, { stock: '英伟达', status: '当前市场多头资金占比,且多头资金持续流入。' }, { stock: '苹果', status: '当前市场多头资金占比,且多头资金持续流入。' } ], // 防抖定时器
debounceTimer: null, // TCP连接相关状态
tcpConnected: false, tcpMessages: [], lastSentMessage: '', // TCP监听器引用,用于移除监听器
connectionListener: null, messageListener: null, // TCP股票数据存储
tcpStockData: { count: 0, data: {}, stock_count: 0, timestamp: '', type: '' }, // TCP数据缓存机制,用于处理分片数据
tcpDataCache: { isCollecting: false, // 是否正在收集数据片段
expectedCount: 0, // 期望的数据总数
collectedData: {}, // 已收集的数据对象(用于对象级拼接)
timestamp: '', // 数据时间戳
type: '', // 数据类型
// 新增:字符串片段缓存
firstPartData: '', // 前半部分数据字符串
isWaitingSecondPart: false // 是否正在等待后半部分数据
}, // 当前显示的3个股票数据(用于MarketOverview组件)
currentStockInfoList: [ { stock_name: '美元/日元', current_price: '151.13', change: '+1.62%', change_value: 0, change_percent: 0 }, { stock_name: '美元/韩元', current_price: '1424.900', change: '-2.92%', change_value: 0, change_percent: 0 }, { stock_name: '美元/英镑', current_price: '0.730', change: '+2.92%', change_value: 0, change_percent: 0 } ] } },
// Vue 2生命周期方法
mounted() { // 状态栏高度
this.iSMT = uni.getSystemInfoSync().statusBarHeight; // 预加载图片资源
this.myStocks.forEach(stock => { // 使用uni.getImageInfo替代Image对象
uni.getImageInfo({ src: stock.chartImg, success: function(res) { // 图片加载成功
console.log('图片预加载成功:', stock.name) }, fail: function(err) { console.log('图片预加载失败:', err) } }) }) // 初始化TCP连接监听器
this.initTcpListeners() // 页面渲染完成后自动连接TCP
this.$nextTick(() => { console.log('页面渲染完成,开始自动连接TCP服务器...') this.connectTcp() }) },
// 页面销毁前的清理工作
onUnload() { // 自动关闭TCP连接
if (this.tcpConnected) { console.log('页面销毁,自动关闭TCP连接') tcpConnection.disconnect() this.tcpConnected = false } // 清理防抖定时器
if (this.debounceTimer) { clearTimeout(this.debounceTimer) this.debounceTimer = null } // 移除TCP监听器,防止内存泄漏
this.removeTcpListeners() }, methods: { goToDeepExploration() { // 跳转到深度探索页面
uni.navigateTo({ url: '/pages/home/deepExploration' }); }, goToMarketSituation() { // 跳转到行情页面
uni.navigateTo({ url: '/pages/home/marketSituation' }); }, // 防抖函数
debounce(fn, delay = 300) { if (this.debounceTimer) clearTimeout(this.debounceTimer) this.debounceTimer = setTimeout(() => { fn() this.debounceTimer = null }, delay) }, // TCP连接相关方法
// 初始化TCP监听器
initTcpListeners() { // 创建连接状态监听器并保存引用
this.connectionListener = (status, result) => { this.tcpConnected = (status === 'connected') console.log('TCP连接状态变化:', status, this.tcpConnected) // 显示连接状态提示
uni.showToast({ title: status === 'connected' ? '连接服务器成功' : '服务器连接断开', icon: status === 'connected' ? 'success' : 'none', duration: 1000 }) // 连接成功后自动发送股票数据请求命令
if (status === 'connected') { console.log('TCP连接成功,自动发送股票数据请求...') // 延迟一小段时间确保连接稳定后再发送命令
setTimeout(() => { this.sendTcpMessage() }, 500) } } // 创建消息监听器并保存引用
this.messageListener = (type, message, parsedArray) => { const messageObj = { type: type, content: message, parsedArray: parsedArray, timestamp: new Date().toLocaleTimeString(), direction: 'received' } console.log("0000") this.tcpMessages.push(messageObj) // console.log('收到TCP消息:', messageObj)
console.log('home开始调用parseStockData',messageObj) // 解析股票数据
this.parseStockData(message) } // 注册监听器
tcpConnection.onConnectionChange(this.connectionListener) tcpConnection.onMessage(this.messageListener) }, // 连接TCP服务器
connectTcp() { console.log('开始连接TCP服务器...') // 先断开现有连接(如果存在)
if (this.tcpConnected) { console.log('检测到现有连接,先断开...') this.disconnectTcp() // 等待断开完成后再连接
setTimeout(() => { this.performTcpConnect() }, 500) } else { // 直接连接
this.performTcpConnect() } }, // 执行TCP连接
performTcpConnect() { console.log('执行TCP连接...') tcpConnection.connect( { ip: '192.168.1.9', port: '8080', channel: '1', // 可选 1~20
charsetname: 'UTF-8' // 默认UTF-8,可选GBK
} ) }, // 断开TCP连接
disconnectTcp() { console.log('断开TCP连接...') tcpConnection.disconnect( { ip: '192.168.1.9', port: '8080', channel: '1', // 可选 1~20
charsetname: 'UTF-8' // 默认UTF-8,可选GBK
} ) this.tcpConnected = false }, // 发送TCP消息
sendTcpMessage() { // 构造要发送的消息对象
const messageData = // {
// command: "real_time",
// stock_code: "SH.000001"
// }
// {"command": "stock_list"}
// {"command": "batch_real_time", "stock_codes": ["SH.000001"]}
{"command": "batch_real_time", "stock_codes": ["SH.000001","SH.000002","SH.000003"]} // 发送消息
const success = tcpConnection.send(messageData) if (success) { console.log('home发送TCP消息:', messageData) uni.showToast({ title: '服务器连接成功', icon: 'success', duration: 1500 }) } }, // 清空消息记录
clearTcpMessages() { this.tcpMessages = [] uni.showToast({ title: '消息记录已清空', icon: 'success', duration: 1500 }) }, // 获取TCP连接状态
getTcpStatus() { const status = tcpConnection.getConnectionStatus() uni.showModal({ title: 'TCP连接状态', content: `当前状态: ${status ? '已连接' : '未连接'}\n消息数量: ${this.tcpMessages.length}`, showCancel: false }) }, // 验证数据完整性(检查count值与data对象个数是否相等)
validateDataIntegrity(parsedMessage) { if (!parsedMessage.count || !parsedMessage.data) { return false } const dataObjectCount = Object.keys(parsedMessage.data).length const expectedCount = parsedMessage.count console.log(`数据完整性验证: 期望${expectedCount}个对象,实际${dataObjectCount}个对象`) return dataObjectCount === expectedCount }, // 检查数据状态(完整数据、前半部分数据、后半部分数据)
getDataStatus(message) { if (typeof message !== 'string') { return 'invalid' } const trimmedMessage = message.trim() const startsWithBrace = trimmedMessage.startsWith('{') const endsWithBrace = trimmedMessage.endsWith('}') if (startsWithBrace && endsWithBrace) { // 以{开头,以}结尾 - 完整数据
console.log('检测到完整数据格式') return 'complete' } else if (startsWithBrace && !endsWithBrace) { // 以{开头,不以}结尾 - 前半部分数据
console.log('检测到前半部分数据') return 'first_part' } else if (!startsWithBrace && endsWithBrace) { // 不以{开头,以}结尾 - 后半部分数据
console.log('检测到后半部分数据') return 'second_part' } else { // 其他情况 - 无效数据
console.log('检测到无效数据格式') return 'invalid' } }, // 缓存前半部分数据
cacheFirstPartData(message) { this.tcpDataCache.firstPartData = message.trim() this.tcpDataCache.isWaitingSecondPart = true console.log('已缓存前半部分数据,长度:', this.tcpDataCache.firstPartData.length) }, // 拼接前后两部分数据
concatenateDataParts(secondPartMessage) { const completeMessage = this.tcpDataCache.firstPartData + secondPartMessage.trim() console.log('数据拼接完成,完整数据长度:', completeMessage.length) // 清空缓存
this.clearStringFragmentCache() return completeMessage }, // 清空字符串片段缓存
clearStringFragmentCache() { this.tcpDataCache.firstPartData = '' this.tcpDataCache.isWaitingSecondPart = false console.log('字符串片段缓存已清空') }, // 合并数据到缓存中
mergeDataToCache(parsedMessage) { // 如果是第一次收集数据,初始化缓存
if (!this.tcpDataCache.isCollecting) { this.tcpDataCache.isCollecting = true this.tcpDataCache.expectedCount = parsedMessage.count this.tcpDataCache.collectedData = {} this.tcpDataCache.timestamp = parsedMessage.timestamp this.tcpDataCache.type = parsedMessage.type console.log('开始收集数据片段,期望总数:', parsedMessage.count) } // 合并新数据到缓存中
if (parsedMessage.data) { Object.assign(this.tcpDataCache.collectedData, parsedMessage.data) console.log('数据片段已合并,当前已收集:', Object.keys(this.tcpDataCache.collectedData).length, '个对象') } }, // 检查缓存数据是否完整
isCacheDataComplete() { const collectedCount = Object.keys(this.tcpDataCache.collectedData).length const expectedCount = this.tcpDataCache.expectedCount console.log(`缓存数据检查: 已收集${collectedCount}个,期望${expectedCount}个`) return collectedCount === expectedCount && collectedCount > 0 }, // 获取完整的缓存数据并清空缓存
getCompleteDataFromCache() { const completeData = { count: this.tcpDataCache.expectedCount, data: { ...this.tcpDataCache.collectedData }, stock_count: this.tcpDataCache.expectedCount, timestamp: this.tcpDataCache.timestamp, type: this.tcpDataCache.type } // 清空缓存
this.tcpDataCache.isCollecting = false this.tcpDataCache.expectedCount = 0 this.tcpDataCache.collectedData = {} this.tcpDataCache.timestamp = '' this.tcpDataCache.type = '' console.log('获取完整数据并清空缓存,数据对象数:', Object.keys(completeData.data).length) return completeData }, // 解析TCP股票数据
parseStockData(message) { try { console.log('进入parseStockData, message类型:', typeof message, '长度:', message.length) // 第一步:检查数据状态(完整、前半部分、后半部分)
const dataStatus = this.getDataStatus(message) let completeMessage = '' switch (dataStatus) { case 'complete': // 完整数据,直接处理
console.log('检测到完整数据,直接处理') completeMessage = message break case 'first_part': // 前半部分数据,缓存并等待后半部分
console.log('检测到前半部分数据,开始缓存') this.cacheFirstPartData(message) return // 等待后半部分数据
case 'second_part': // 后半部分数据,检查是否有缓存的前半部分
if (this.tcpDataCache.isWaitingSecondPart) { console.log('检测到后半部分数据,开始拼接') completeMessage = this.concatenateDataParts(message) } else { console.log('收到后半部分数据,但没有缓存的前半部分,跳过处理') return } break case 'invalid': default: console.log('数据格式无效,跳过处理') return } // 第二步:解析完整的JSON数据
let parsedMessage try { console.log('开始解析完整JSON数据,长度:', completeMessage.length) parsedMessage = JSON.parse(completeMessage) console.log('JSON解析成功,解析后类型:', typeof parsedMessage, parsedMessage) } catch (parseError) { console.error('JSON解析失败:', parseError.message) // 清空字符串片段缓存
this.clearStringFragmentCache() return } // 第三步:检查是否是股票数据类型
if (!((parsedMessage.type === 'batch_data_chunk' || parsedMessage.type === 'batch_realtime_data') && parsedMessage.data)) { console.log('不是batch_data_chunk或batch_realtime_data类型的消息,跳过处理') return } console.log('开始处理股票数据') // 第四步:验证数据完整性(对象级别的完整性检查)
// 注意:batch_data_chunk类型的数据不需要验证完整性,直接处理
if (parsedMessage.type === 'batch_data_chunk') { console.log('batch_data_chunk类型数据,跳过完整性验证,直接处理') this.processCompleteStockData(parsedMessage) } else { const isDataComplete = this.validateDataIntegrity(parsedMessage) if (isDataComplete) { // 数据完整,直接处理
console.log('对象级数据完整,直接处理') this.processCompleteStockData(parsedMessage) } else { // 数据不完整,需要拼接(对象级别的拼接)
console.log('对象级数据不完整,开始拼接处理') // 将当前数据合并到缓存中
this.mergeDataToCache(parsedMessage) // 检查缓存中的数据是否已经完整
if (this.isCacheDataComplete()) { console.log('缓存数据已完整,开始处理') const completeData = this.getCompleteDataFromCache() this.processCompleteStockData(completeData) } else { console.log('缓存数据仍不完整,等待更多数据片段') } } } } catch (error) { console.error('解析TCP股票数据失败:', error.message) console.error('错误详情:', error) // 发生错误时清空所有缓存
this.clearStringFragmentCache() if (this.tcpDataCache.isCollecting) { console.log('发生错误,清空对象级数据缓存') this.tcpDataCache.isCollecting = false this.tcpDataCache.expectedCount = 0 this.tcpDataCache.collectedData = {} this.tcpDataCache.timestamp = '' this.tcpDataCache.type = '' } } }, // 处理完整的股票数据
processCompleteStockData(completeData) { console.log("开始更新TCP股票数据存储") // 更新TCP股票数据存储
this.tcpStockData = { count: completeData.count || 0, data: completeData.data || {}, stock_count: completeData.stock_count || 0, timestamp: completeData.timestamp || '', type: completeData.type || '' } // 获取所有股票的数据用于显示(最多3个)
const stockCodes = Object.keys(completeData.data) const newStockInfoList = [] // 处理最多3个股票数据
for (let i = 0; i < Math.min(stockCodes.length, 3); i++) { const stockCode = stockCodes[i] // 检查数据结构
if (completeData.data[stockCode] && Array.isArray(completeData.data[stockCode]) && completeData.data[stockCode].length > 0) { const stockData = completeData.data[stockCode][0] // 取第一条数据
if (stockData && stockData.current_price !== undefined && stockData.pre_close !== undefined) { // 计算涨跌幅
const changeValue = stockData.current_price - stockData.pre_close const changePercent = ((changeValue / stockData.pre_close) * 100).toFixed(2) const changeSign = changeValue >= 0 ? '+' : '' // 添加到股票信息列表
newStockInfoList.push({ stock_name: stockData.stock_name || '未知股票', current_price: stockData.current_price ? stockData.current_price.toFixed(2) : '0.00', change: `${changeSign}${changePercent}%`, change_value: changeValue, change_percent: parseFloat(changePercent) }) } } } // 更新当前显示的股票信息列表
if (newStockInfoList.length > 0) { this.currentStockInfoList = newStockInfoList console.log('股票数据更新成功,共', newStockInfoList.length, '个股票:', this.currentStockInfoList) } }, // 移除TCP监听器
removeTcpListeners() { if (this.connectionListener) { tcpConnection.removeConnectionListener(this.connectionListener) this.connectionListener = null console.log('已移除TCP连接状态监听器') } if (this.messageListener) { tcpConnection.removeMessageListener(this.messageListener) this.messageListener = null console.log('已移除TCP消息监听器') } } }}</script>
<style scoped>.main { display: flex; flex-direction: column; height: 100vh; background-color: #ffffff;}
.header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background-color: #ffffff;}
.title { font-size: 22px; font-weight: bold; text-align: center; flex: 1;}
.headphone-icon, .notification-icon { width: 40px; display: flex; align-items: center; justify-content: center;}
.header-icon-image { width: 24px; height: 24px; object-fit: contain;}
.content-container { flex: 1; padding: 10px; width: 100%; box-sizing: border-box; overflow-x: hidden;}
.section { margin-bottom: 15px; /* background-color: #f5f5f5; */ background-color: #ffffff; border-radius: 8px; padding: 15px;}
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px;}
.section-title { font-size: 18px; font-weight: bold; color: #000000;}.section-title-text{ font-size: 14px;
}
.text-gap{ height: 10px;}
.more-btn { font-size: 12px; font-weight: bold; color: #ffffff; background-color: #000000; padding: 4px 8px; border-radius: 4px;}
/* 市场概览样式 */.market-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background-color: #ffffff; margin-bottom: 10px;}
.market-content { background-color: #f5f5f5; border-radius: 8px; padding: 15px; margin: 0 15px;}
.market-image { margin-bottom: 15px; display: flex; justify-content: center;}
.overview-image { width: 100%; border-radius: 8px;}
.market-data { display: flex; flex-direction: column; gap: 10px;}
/* 间隔样式 */.section-gap { height: 20px;}
.market-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f0f0;}
.down { color: #ff4d4f;}
.up { color: #52c41a;}
/* DeepMate样式 */.deepmate-container { background-color: #ffe6e6; border-radius: 10px; padding: 15px; margin: 0 15px;}
.deepmate-header { margin-bottom: 10px;}
.title-container { display: flex; align-items: center; justify-content: space-between; width: 100%;}
.title-left { width: 50%;}
.title-right { width: 50%; display: flex; justify-content: flex-end;}
.deepmate-title { font-size: 18px; font-weight: bold; color: #ff4d4f;}
.deepmate-icon { width: 60px; height: 60px; margin-left: 0;}
.deepmate-subtitle { font-size: 12px; color: #666; margin-left: 5px;}
.deepmate-hotspots { margin: 10px 0;}
.hotspot-item { background-color: #f5f5f5; padding: 8px 12px; border-radius: 6px; margin-bottom: 8px;}
.hotspot-item text { font-size: 14px; color: #333;}
.deepmate-action { display: flex; justify-content: center; align-items: center; background-color: #ffffff; border-radius: 20px; padding: 10px; margin-top: 10px;}
/* 欢迎部分样式 */.welcome-section { margin-bottom: 15px; padding: 0;}
.welcome-swiper { width: 100%; height: 150px; border-radius: 0; overflow: hidden;}
.deepmate-section { padding: 0;}
.swiper-image { width: 100%; height: 100%; border-radius: 8px; object-fit: contain;}
/* 深度探索样式 */.deep-exploration { margin-top: 15px; padding: 0; /* 移除内边距,让子容器自己控制 */}
.section-header-container { margin-bottom: 10px;}
.section-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background-color: #ffffff;}
.header-left { display: flex; align-items: center;}
.header-right { display: flex; align-items: center;}
.section-title { font-size: 16px; font-weight: bold; color: #333;}
.more-btn { font-size: 12px; color: #ffffff;}
.exploration-container { border-radius: 8px; overflow: hidden;}
.exploration-content { display: flex; justify-content: space-between; padding: 15px; background-color: #f5f5f5; border-radius: 8px;}
.exploration-item { display: flex; flex-direction: column; align-items: center; width: 22%; background-color: #ffffff; border-radius: 8px; padding: 10px 0;}
.exploration-icon { width: 50px; height: 50px; margin-bottom: 8px;}
.exploration-text { font-size: 12px; color: #333;}
.icon-text { font-size: 12px;}
/* 我的自选样式 */.my-selection { padding: 0; /* 移除内边距,让子容器自己控制 */}
.stock-container { border-radius: 8px; overflow: hidden; background-color: #f5f5f5; padding: 15px; box-sizing: border-box;}
.stock-list { display: flex; flex-direction: row; justify-content: center; background-color: #f8f8f8; border-radius: 8px; padding: 15px; gap: 10px; /* 添加卡片之间的间距 */}
.stock-item { display: flex; flex-direction: column; justify-content: space-between; padding: 3px; background-color: #ffffff; border-radius: 8px; width: 30%; box-shadow: 0 2px 5px rgba(0,0,0,0.1); will-change: transform; transform: translateZ(0);}
.stock-info { display: flex; flex-direction: column; align-items: center; margin-bottom: 5px;}
.stock-chart { display: flex; align-items: center; justify-content: center;}
.name-container { display: flex; align-items: center; gap: 5px;}
.stock-name { font-size: 12px; font-weight: bold; color: #333;}
.stock-code-label { font-size: 12px; color: #666; background-color: #f5f5f5; padding: 0 4px; border-radius: 3px;}
.stock-price { font-size: 12px; color: #666;}
.price-container { display: flex; align-items: center; gap: 5px;}
.stock-change { font-size: 12px;}
.stock-up { color: #4cd964;}
.stock-down { color: #ff3b30;}
.chart-image { width: 100px; height: 40px;}
/* 市场看点样式 */.highlights-title-container { background-color: #ffffff; margin-bottom: 10px;}
.highlights-image-container { background-color: #f5f5f5; border-radius: 8px; padding: 10px; margin-bottom: 15px;}
.highlights-image { width: 100%; height: 150px; border-radius: 4px;}
/* 机构动向简报样式 */.institutional-reports { margin-top: 15px; background-color: #f8f8f8; border-radius: 8px; padding: 10px;}
.section-title-container { position: relative; padding-left: 10px; margin-bottom: 5px;}
.section-title-container:before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background-color: #ff4d4f; border-radius: 2px;}
.report-item { background-color: #ffffff; border-radius: 8px; padding: 10px; margin-bottom: 8px;}
.report-stock { font-size: 14px; font-weight: bold; color: #e74c3c; margin-bottom: 5px;}
.report-status { font-size: 12px; color: #333;}
.view-more { text-align: center; color: #1890ff; font-size: 12px; padding: 5px;}
/* 底部空间 */.bottom-space { height: 60px;}
/* 免责声明样式 */.disclaimer-text { font-size: 8px; color: #999999; text-align: center;}
/* 底部导航 */.static-footer { position: fixed; bottom: 0; width: 100%;}
/* TCP测试区域样式 */.tcp-test-section { border: 1px solid #e0e0e0; background-color: #f9f9f9;}
.tcp-status { padding: 4px 12px; border-radius: 12px; font-size: 12px;}
.tcp-status.connected { background-color: #e8f5e8; color: #4caf50; border: 1px solid #4caf50;}
.tcp-status.disconnected { background-color: #ffeaea; color: #f44336; border: 1px solid #f44336;}
.status-text { font-size: 12px; font-weight: bold;}
.tcp-controls { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 15px;}
.tcp-btn { flex: 1; min-width: 80px; height: 36px; border-radius: 6px; font-size: 12px; border: none; color: white; font-weight: bold;}
.connect-btn { background-color: #4caf50;}
.connect-btn:disabled { background-color: #cccccc;}
.disconnect-btn { background-color: #f44336;}
.disconnect-btn:disabled { background-color: #cccccc;}
.send-btn { background-color: #2196f3;}
.send-btn:disabled { background-color: #cccccc;}
.status-btn { background-color: #ff9800;}
.tcp-messages { margin-top: 15px; border-top: 1px solid #e0e0e0; padding-top: 15px;}
.messages-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;}
.messages-title { font-size: 14px; font-weight: bold; color: #333;}
.clear-btn { padding: 4px 8px; background-color: #f44336; color: white; border: none; border-radius: 4px; font-size: 10px;}
.messages-list { max-height: 200px; border: 1px solid #e0e0e0; border-radius: 6px; padding: 8px; background-color: white;}
.message-item { margin-bottom: 8px; padding: 8px; border-radius: 6px; border-left: 3px solid #ccc;}
.message-item.sent { background-color: #e3f2fd; border-left-color: #2196f3;}
.message-item.received { background-color: #f1f8e9; border-left-color: #4caf50;}
.message-info { display: flex; justify-content: space-between; margin-bottom: 4px;}
.message-direction { font-size: 10px; font-weight: bold; color: #666;}
.message-time { font-size: 10px; color: #999;}
.message-content { font-size: 12px; color: #333; word-break: break-all;}</style>
|