You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1878 lines
51 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="main">
  3. <!-- 顶部状态栏占位 -->
  4. <view class="top" :style="{height:iSMT+'px'}"></view>
  5. <!-- 头部导航 -->
  6. <view class="header">
  7. <view class="headphone-icon">
  8. <image src="https://d31zlh4on95l9h.cloudfront.net/images/bef2edba6cc0c85671fde07cfab5270d.png" class="header-icon-image"></image>
  9. </view>
  10. <view class="title">DeepChart</view>
  11. <view class="notification-icon">
  12. <image src="https://d31zlh4on95l9h.cloudfront.net/images/2554c84b91712d2a6cb6b00380e63bac.png" class="header-icon-image"></image>
  13. </view>
  14. </view>
  15. <!-- 内容区域 - 使用滚动视图 -->
  16. <scroll-view scroll-y class="content-container">
  17. <!-- 1. 今日市场概览 -->
  18. <market-overview :stock-info-list="currentStockInfoList"></market-overview>
  19. <!-- 间隔 -->
  20. <view class="section-gap"></view>
  21. <!-- 新增欢迎部分 -->
  22. <view class="section welcome-section">
  23. <!-- 轮播图 -->
  24. <swiper class="welcome-swiper" circular autoplay interval="3000" duration="500" indicator-dots indicator-active-color="#4080ff">
  25. <swiper-item v-for="(item, index) in 5" :key="index">
  26. <image class="swiper-image" src="https://d31zlh4on95l9h.cloudfront.net/images/e4272cc034fa2a3d1ca588ef84e51ab0.png" mode="aspectFill"></image>
  27. </swiper-item>
  28. </swiper>
  29. </view>
  30. <!-- 2. DeepMate -->
  31. <view class="section deepmate-section">
  32. <DeepMate />
  33. </view>
  34. <!-- 3. 深度探索 -->
  35. <view class="section deep-exploration">
  36. <!-- 上部分标题和查看更多按钮 -->
  37. <view class="section-header-container">
  38. <view class="section-header">
  39. <view class="header-left">
  40. <text class="section-title">深度探索</text>
  41. </view>
  42. <view class="header-right">
  43. <text class="more-btn">查看更多</text>
  44. </view>
  45. </view>
  46. </view>
  47. <!-- 下部分四个图标 -->
  48. <view class="exploration-container">
  49. <view class="exploration-content">
  50. <view class="exploration-item">
  51. <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/199472b0ee90a1c897f7c87b85accd84.png" mode="aspectFit" lazy-load="true"></image>
  52. <text class="exploration-text">主力追踪</text>
  53. </view>
  54. <view class="exploration-item">
  55. <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/c25ca5e176efc961dabfa5d0d1b486b0.png" mode="aspectFit" lazy-load="true"></image>
  56. <text class="exploration-text">主力资达</text>
  57. </view>
  58. <view class="exploration-item">
  59. <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/c064d7066dc8129a7df7b052762f82cf.png" mode="aspectFit" lazy-load="true"></image>
  60. <text class="exploration-text">主力解码</text>
  61. </view>
  62. <view class="exploration-item">
  63. <image class="exploration-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/9d69cceee9c515911477078af6f68d88.png" mode="aspectFit" lazy-load="true"></image>
  64. <text class="exploration-text">主力资金流</text>
  65. </view>
  66. </view>
  67. </view>
  68. </view>
  69. <!-- 4. 我的自选 -->
  70. <view class="section my-selection">
  71. <!-- 上部分标题和查看更多按钮 -->
  72. <view class="section-header-container">
  73. <view class="section-header">
  74. <text class="section-title">我的自选</text>
  75. <text class="more-btn">添加自选股</text>
  76. </view>
  77. <!-- 我的自选TCP连接状态和控制 -->
  78. <!-- <view class="my-stocks-tcp-control">
  79. <view class="tcp-status" :class="{'connected': myStocksTcpConnected, 'disconnected': !myStocksTcpConnected}">
  80. <text class="status-text">{{myStocksTcpConnected ? '自选已连接' : '自选未连接'}}</text>
  81. </view>
  82. <view class="tcp-buttons">
  83. <button class="tcp-btn connect-btn" @click="connectMyStocksTcp" :disabled="myStocksTcpConnected">连接</button>
  84. <button class="tcp-btn disconnect-btn" @click="disconnectMyStocksTcp" :disabled="!myStocksTcpConnected">断开</button>
  85. </view>
  86. </view> -->
  87. </view>
  88. <!-- 下部分股票列表 -->
  89. <view class="stock-container">
  90. <view class="stock-list">
  91. <!-- 如果有TCP数据显示实时数据否则显示静态数据 -->
  92. <view class="stock-item" v-for="(item, index) in (myStocksInfoList.length > 0 ? myStocksInfoList : myStocks)" :key="item.code || item.stock_code">
  93. <view class="stock-info">
  94. <view class="name-container">
  95. <text class="stock-name">{{item.name || item.stock_name}}</text>
  96. <text class="stock-code-label">{{item.code || item.stock_code}}</text>
  97. </view>
  98. <view class="price-container">
  99. <text class="stock-price">{{item.price || item.current_price}}</text>
  100. <text class="stock-change" :class="{'stock-up': (item.change_percent || item.change || item.change_amount || 0) > 0, 'stock-down': (item.change_percent || item.change || item.change_amount || 0) < 0}">
  101. {{(item.change_percent || item.change || item.change_amount || 0) > 0 ? '+' : ''}}{{(parseFloat(item.change_percent || item.change || item.change_amount || 0)).toFixed(2)}}%
  102. </text>
  103. </view>
  104. </view>
  105. <view class="stock-chart">
  106. <image :src="item.chartImg || '/static/c5.png'" mode="aspectFit" class="chart-image"></image>
  107. </view>
  108. </view>
  109. </view>
  110. <!-- 机构动向简报数据 -->
  111. <view class="institutional-reports">
  112. <view class="section-title-container">
  113. <text class="section-title-text">机构动向简报</text>
  114. </view>
  115. <view class="text-gap"></view>
  116. <view class="report-item" v-for="(report, index) in institutionalReports" :key="index">
  117. <view class="report-stock">{{report.stock}}</view>
  118. <view class="report-status">{{report.status}}</view>
  119. </view>
  120. <view class="view-more">
  121. <text>查看更多 >></text>
  122. </view>
  123. </view>
  124. </view>
  125. </view>
  126. <!-- 5. TCP连接测试 -->
  127. <!-- <view class="section tcp-test-section">
  128. <view class="section-header">
  129. <text class="section-title">TCP连接测试</text>
  130. <view class="tcp-status" :class="{'connected': tcpConnected, 'disconnected': !tcpConnected}">
  131. <text class="status-text">{{tcpConnected ? '已连接' : '未连接'}}</text>
  132. </view>
  133. </view> -->
  134. <!-- TCP控制按钮 -->
  135. <!-- <view class="tcp-controls">
  136. <button class="tcp-btn connect-btn" @click="connectTcp" :disabled="tcpConnected">连接</button>
  137. <button class="tcp-btn disconnect-btn" @click="disconnectTcp" :disabled="!tcpConnected">断开</button>
  138. <button class="tcp-btn send-btn" @click="sendTcpMessage" :disabled="!tcpConnected">发送测试消息</button>
  139. <button class="tcp-btn status-btn" @click="getTcpStatus">查看状态</button>
  140. </view> -->
  141. <!-- 消息记录 -->
  142. <!-- <view class="tcp-messages" v-if="tcpMessages.length > 0">
  143. <view class="messages-header">
  144. <text class="messages-title">消息记录 ({{tcpMessages.length}})</text>
  145. <button class="clear-btn" @click="clearTcpMessages">清空</button>
  146. </view>
  147. <scroll-view scroll-y class="messages-list" scroll-top="{{tcpMessages.length * 50}}">
  148. <view class="message-item" v-for="(msg, index) in tcpMessages" :key="index"
  149. :class="{'sent': msg.direction === 'sent', 'received': msg.direction === 'received'}">
  150. <view class="message-info">
  151. <text class="message-direction">{{msg.direction === 'sent' ? '发送' : '接收'}}</text>
  152. <text class="message-time">{{msg.timestamp}}</text>
  153. </view>
  154. <text class="message-content">{{msg.content}}</text>
  155. </view>
  156. </scroll-view>
  157. </view>
  158. </view> -->
  159. <!-- 6. 今日市场看点 -->
  160. <view class="section-header highlights-title-container">
  161. <text class="section-title">今日市场核心看点</text>
  162. </view>
  163. <view class="highlights-image-container">
  164. <image src="https://d31zlh4on95l9h.cloudfront.net/images/8d5365af968402a18cedb120c09460b0.png" mode="aspectFit" class="highlights-image"></image>
  165. </view>
  166. <!-- 底部空间 - 为底部导航腾出空间 -->
  167. <view class="bottom-space"></view>
  168. </scroll-view>
  169. <!-- 底部导航 -->
  170. <footerBar class="static-footer" :type="type"></footerBar>
  171. </view>
  172. </template>
  173. <script>
  174. import footerBar from '../../components/footerBar.vue'
  175. import MarketOverview from '../../components/MarketOverview.vue'
  176. import DeepMate from '../../components/DeepMate.vue'
  177. import tcpConnection from '../../api/tcpConnection.js'
  178. import th from '../../static/language/th'
  179. import MySelectionsAPI from '../../api/home/mySelections.js'
  180. export default {
  181. components: {
  182. footerBar,
  183. MarketOverview,
  184. DeepMate
  185. },
  186. data() {
  187. return {
  188. type: 'home',
  189. iSMT: 0,
  190. // 深度探索数据
  191. explorationItems: [
  192. { title: '主力追踪', icon: '/static/c1.png' },
  193. { title: '主力资金', icon: '/static/c2.png' },
  194. { title: '主力解码', icon: '/static/c3.png' },
  195. { title: '主力资金流', icon: '/static/c4.png' }
  196. ],
  197. // 我的自选股票数据
  198. myStocks: [
  199. { name: '特斯拉', code: 'TSLA', price: '482.00', change: 2.80, chartImg: '/static/c5.png' },
  200. { name: '英伟达', code: 'NVDA', price: '189.800', change: -2.92, chartImg: '/static/c6.png' },
  201. { name: '苹果', code: 'AAPL', price: '256.430', change: 2.60, chartImg: '/static/c7.png' }
  202. ],
  203. // 机构动向简报数据
  204. institutionalReports: [
  205. { stock: '特斯拉', status: '当前市场多头资金占比,且多头资金持续流入。' },
  206. { stock: '英伟达', status: '当前市场多头资金占比,且多头资金持续流入。' },
  207. { stock: '苹果', status: '当前市场多头资金占比,且多头资金持续流入。' }
  208. ],
  209. // 防抖定时器
  210. debounceTimer: null,
  211. // TCP连接相关状态
  212. tcpConnected: false,
  213. tcpMessages: [],
  214. lastSentMessage: '',
  215. // 我的自选TCP连接状态
  216. myStocksTcpConnected: false,
  217. // TCP监听器引用,用于移除监听器
  218. connectionListener: null,
  219. messageListener: null,
  220. // TCP股票数据存储
  221. tcpStockData: {
  222. count: 0,
  223. data: {},
  224. stock_count: 0,
  225. timestamp: '',
  226. type: ''
  227. },
  228. // 我的自选TCP股票数据存储
  229. myStocksTcpData: {
  230. count: 0,
  231. data: {},
  232. stock_count: 0,
  233. timestamp: '',
  234. type: ''
  235. },
  236. // TCP数据缓存机制,用于处理分片数据
  237. tcpDataCache: {
  238. isCollecting: false, // 是否正在收集数据片段
  239. expectedCount: 0, // 期望的数据总数
  240. collectedData: {}, // 已收集的数据对象(用于对象级拼接)
  241. timestamp: '', // 数据时间戳
  242. type: '', // 数据类型
  243. // 新增:字符串片段缓存
  244. firstPartData: '', // 前半部分数据字符串
  245. isWaitingSecondPart: false // 是否正在等待后半部分数据
  246. },
  247. // 我的自选TCP数据缓存机制,用于处理分片数据
  248. myStocksTcpDataCache: {
  249. isCollecting: false, // 是否正在收集数据片段
  250. expectedCount: 0, // 期望的数据总数
  251. collectedData: {}, // 已收集的数据对象(用于对象级拼接)
  252. timestamp: '', // 数据时间戳
  253. type: '', // 数据类型
  254. // 字符串片段缓存
  255. firstPartData: '', // 前半部分数据字符串
  256. isWaitingSecondPart: false // 是否正在等待后半部分数据
  257. },
  258. // 当前显示的3个股票数据(用于MarketOverview组件)
  259. currentStockInfoList: [
  260. {
  261. stock_name: '美元/日元',
  262. current_price: '151.13',
  263. change: '+1.62%',
  264. change_value: 0,
  265. change_percent: 0
  266. },
  267. {
  268. stock_name: '美元/韩元',
  269. current_price: '1424.900',
  270. change: '-2.92%',
  271. change_value: 0,
  272. change_percent: 0
  273. },
  274. {
  275. stock_name: '美元/英镑',
  276. current_price: '0.730',
  277. change: '+2.92%',
  278. change_value: 0,
  279. change_percent: 0
  280. }
  281. ],
  282. // 我的自选处理后的股票信息列表
  283. myStocksInfoList: []
  284. }
  285. },
  286. // Vue 2生命周期方法
  287. mounted() {
  288. // 状态栏高度
  289. this.iSMT = uni.getSystemInfoSync().statusBarHeight;
  290. // 预加载图片资源
  291. this.myStocks.forEach(stock => {
  292. // 使用uni.getImageInfo替代Image对象
  293. uni.getImageInfo({
  294. src: stock.chartImg,
  295. success: function(res) {
  296. // 图片加载成功
  297. console.log('图片预加载成功:', stock.name)
  298. },
  299. fail: function(err) {
  300. console.log('图片预加载失败:', err)
  301. }
  302. })
  303. })
  304. // 初始化TCP连接监听器
  305. this.initTcpListeners()
  306. // 页面渲染完成后自动连接两个TCP连接
  307. // this.$nextTick(() => {
  308. // console.log('页面渲染完成,开始自动连接TCP服务器...')
  309. // // 延迟一小段时间确保页面完全加载后再连接
  310. // setTimeout(() => {
  311. // // 连接今日市场概览TCP(channel 1)
  312. // console.log('连接今日市场概览TCP(channel 1)...')
  313. // this.connectTcp()
  314. // // 稍微延迟后连接我的自选TCP(channel 2)
  315. // setTimeout(() => {
  316. // console.log('连接我的自选TCP(channel 2)...')
  317. // this.connectMyStocksTcp()
  318. // }, 500)
  319. // }, 1000)
  320. // })
  321. },
  322. // 页面销毁前的清理工作
  323. onUnload() {
  324. // 自动关闭主TCP连接
  325. if (this.tcpConnected) {
  326. console.log('页面销毁,自动关闭主TCP连接')
  327. tcpConnection.disconnect({
  328. ip: '192.168.1.9',
  329. port: '8080',
  330. channel: '1',
  331. charsetname: 'UTF-8'
  332. })
  333. this.tcpConnected = false
  334. }
  335. // 自动关闭我的自选TCP连接
  336. if (this.myStocksTcpConnected) {
  337. console.log('页面销毁,自动关闭我的自选TCP连接')
  338. tcpConnection.disconnect({
  339. ip: '192.168.1.9',
  340. port: '8080',
  341. channel: '2',
  342. charsetname: 'UTF-8'
  343. })
  344. this.myStocksTcpConnected = false
  345. }
  346. // 清理防抖定时器
  347. if (this.debounceTimer) {
  348. clearTimeout(this.debounceTimer)
  349. this.debounceTimer = null
  350. }
  351. // 移除TCP监听器,防止内存泄漏
  352. this.removeTcpListeners()
  353. },
  354. methods: {
  355. // 防抖函数
  356. debounce(fn, delay = 300) {
  357. if (this.debounceTimer) clearTimeout(this.debounceTimer)
  358. this.debounceTimer = setTimeout(() => {
  359. fn()
  360. this.debounceTimer = null
  361. }, delay)
  362. },
  363. // TCP连接相关方法
  364. // 初始化TCP监听器
  365. initTcpListeners() {
  366. // 创建连接状态监听器并保存引用
  367. this.connectionListener = (status, result, channel) => {
  368. console.log('TCP连接状态变化:', status, 'channel:', channel)
  369. // 根据channel更新对应的连接状态
  370. if (channel === '1') {
  371. // 主TCP连接
  372. this.tcpConnected = (status === 'connected')
  373. console.log('主TCP连接状态:', this.tcpConnected)
  374. // 显示连接状态提示
  375. uni.showToast({
  376. title: status === 'connected' ? '主连接服务器成功' : '主服务器连接断开',
  377. icon: status === 'connected' ? 'success' : 'none',
  378. duration: 1000
  379. })
  380. // 连接成功后自动发送股票数据请求命令
  381. if (status === 'connected') {
  382. console.log('主TCP连接成功,自动发送股票数据请求...')
  383. // 延迟一小段时间确保连接稳定后再发送命令
  384. setTimeout(() => {
  385. this.sendTcpMessage()
  386. }, 500)
  387. }
  388. } else if (channel === '2') {
  389. // 我的自选TCP连接
  390. this.myStocksTcpConnected = (status === 'connected')
  391. console.log('我的自选TCP连接状态:', this.myStocksTcpConnected)
  392. // 显示连接状态提示
  393. uni.showToast({
  394. title: status === 'connected' ? '我的自选连接成功' : '我的自选连接断开',
  395. icon: status === 'connected' ? 'success' : 'none',
  396. duration: 1000
  397. })
  398. // 连接成功后自动发送我的自选股票数据请求
  399. if (status === 'connected') {
  400. console.log('我的自选TCP连接成功,自动发送自选股票数据请求...')
  401. // 延迟一小段时间确保连接稳定后再发送命令
  402. setTimeout(() => {
  403. this.sendMyStocksTcpMessage()
  404. }, 500)
  405. }
  406. }
  407. }
  408. // 创建消息监听器并保存引用
  409. this.messageListener = (type, message, parsedArray, channel) => {
  410. const messageObj = {
  411. type: type,
  412. content: message,
  413. parsedArray: parsedArray,
  414. channel: channel,
  415. timestamp: new Date().toLocaleTimeString(),
  416. direction: 'received'
  417. }
  418. this.tcpMessages.push(messageObj)
  419. console.log('收到TCP消息:', messageObj)
  420. console.log('消息来源channel:', channel)
  421. // 根据channel调用相应的数据处理方法
  422. if (channel === '1') {
  423. // 主TCP连接 - 今日市场概览数据
  424. // console.log('处理今日市场概览数据')
  425. this.parseStockData(message)
  426. } else if (channel === '2') {
  427. // 我的自选TCP连接 - 我的自选数据
  428. console.log('处理我的自选数据')
  429. this.parseMyStocksData(message)
  430. } else {
  431. console.warn('未知的channel:', channel)
  432. }
  433. }
  434. // 注册监听器
  435. tcpConnection.onConnectionChange(this.connectionListener)
  436. tcpConnection.onMessage(this.messageListener)
  437. },
  438. // 连接TCP服务器
  439. connectTcp() {
  440. // console.log('开始连接TCP服务器...')
  441. // 先断开现有连接(如果存在)
  442. if (this.tcpConnected) {
  443. // console.log('检测到现有连接,先断开...')
  444. this.disconnectTcp()
  445. // 等待断开完成后再连接
  446. setTimeout(() => {
  447. this.performTcpConnect()
  448. }, 500)
  449. } else {
  450. // 直接连接
  451. this.performTcpConnect()
  452. }
  453. },
  454. // 执行TCP连接
  455. performTcpConnect() {
  456. // console.log('执行TCP连接...')
  457. tcpConnection.connect(
  458. {
  459. ip: '192.168.1.9',
  460. port: '8080',
  461. channel: '1', // 可选 1~20
  462. charsetname: 'UTF-8' // 默认UTF-8,可选GBK
  463. }
  464. )
  465. },
  466. // 断开TCP连接
  467. disconnectTcp() {
  468. // console.log('断开TCP连接...')
  469. tcpConnection.disconnect(
  470. {
  471. ip: '192.168.1.9',
  472. port: '8080',
  473. channel: '1', // 可选 1~20
  474. charsetname: 'UTF-8' // 默认UTF-8,可选GBK
  475. }
  476. )
  477. this.tcpConnected = false
  478. },
  479. // 连接我的自选TCP服务器
  480. connectMyStocksTcp() {
  481. console.log('开始连接我的自选TCP服务器...')
  482. // 先断开现有连接(如果存在)
  483. if (this.myStocksTcpConnected) {
  484. console.log('检测到我的自选现有连接,先断开...')
  485. this.disconnectMyStocksTcp()
  486. // 等待断开完成后再连接
  487. setTimeout(() => {
  488. this.performMyStocksTcpConnect()
  489. }, 500)
  490. } else {
  491. // 直接连接
  492. this.performMyStocksTcpConnect()
  493. }
  494. },
  495. // 执行我的自选TCP连接
  496. performMyStocksTcpConnect() {
  497. console.log('执行我的自选TCP连接...')
  498. tcpConnection.connect(
  499. {
  500. ip: '192.168.1.9',
  501. port: '8080',
  502. channel: '2', // 我的自选使用channel 2
  503. charsetname: 'UTF-8' // 默认UTF-8,可选GBK
  504. }
  505. )
  506. },
  507. // 断开我的自选TCP连接
  508. disconnectMyStocksTcp() {
  509. console.log('断开我的自选TCP连接...')
  510. tcpConnection.disconnect(
  511. {
  512. ip: '192.168.1.9',
  513. port: '8080',
  514. channel: '2', // 我的自选使用channel 2
  515. charsetname: 'UTF-8' // 默认UTF-8,可选GBK
  516. }
  517. )
  518. this.myStocksTcpConnected = false
  519. },
  520. // 解析我的自选TCP股票数据
  521. parseMyStocksData(message) {
  522. try {
  523. console.log('进入parseMyStocksData, message类型:', typeof message, '长度:', message.length)
  524. // 第一步:检查数据状态(完整、前半部分、后半部分)
  525. const dataStatus = this.getMyStocksDataStatus(message)
  526. let completeMessage = ''
  527. switch (dataStatus) {
  528. case 'complete':
  529. // 完整数据,直接处理
  530. console.log('我的自选:检测到完整数据,直接处理')
  531. completeMessage = message
  532. break
  533. case 'first_part':
  534. // 前半部分数据,缓存并等待后半部分
  535. console.log('我的自选:检测到前半部分数据,开始缓存')
  536. this.cacheMyStocksFirstPartData(message)
  537. return // 等待后半部分数据
  538. case 'second_part':
  539. // 后半部分数据,检查是否有缓存的前半部分
  540. if (this.myStocksTcpDataCache.isWaitingSecondPart) {
  541. console.log('我的自选:检测到后半部分数据,开始拼接')
  542. completeMessage = this.concatenateMyStocksDataParts(message)
  543. } else {
  544. console.log('我的自选:收到后半部分数据,但没有缓存的前半部分,跳过处理')
  545. return
  546. }
  547. break
  548. case 'invalid':
  549. default:
  550. console.log('我的自选:数据格式无效,跳过处理')
  551. return
  552. }
  553. // 第二步:解析完整的JSON数据
  554. let parsedMessage
  555. try {
  556. console.log('我的自选:开始解析完整JSON数据,长度:', completeMessage.length)
  557. parsedMessage = JSON.parse(completeMessage)
  558. console.log('我的自选:JSON解析成功,解析后类型:', typeof parsedMessage, parsedMessage)
  559. } catch (parseError) {
  560. console.error('我的自选:JSON解析失败:', parseError.message)
  561. // 清空字符串片段缓存
  562. this.clearMyStocksStringFragmentCache()
  563. return
  564. }
  565. // 第三步:检查是否是股票数据类型
  566. if (!((parsedMessage.type === 'batch_data_chunk' || parsedMessage.type === 'batch_realtime_data') && parsedMessage.data)) {
  567. console.log('我的自选:不是batch_data_chunk或batch_realtime_data类型的消息,跳过处理')
  568. return
  569. }
  570. console.log('我的自选:开始处理股票数据')
  571. // 第四步:验证数据完整性(对象级别的完整性检查)
  572. // 注意:batch_data_chunk类型的数据不需要验证完整性,直接处理
  573. if (parsedMessage.type === 'batch_data_chunk') {
  574. console.log('我的自选:batch_data_chunk类型数据,跳过完整性验证,直接处理')
  575. this.processCompleteMyStocksData(parsedMessage)
  576. } else {
  577. const isDataComplete = this.validateMyStocksDataIntegrity(parsedMessage)
  578. if (isDataComplete) {
  579. // 数据完整,直接处理
  580. console.log('我的自选:对象级数据完整,直接处理')
  581. this.processCompleteMyStocksData(parsedMessage)
  582. } else {
  583. // 数据不完整,需要拼接(对象级别的拼接)
  584. console.log('我的自选:对象级数据不完整,开始拼接处理')
  585. // 将当前数据合并到缓存中
  586. this.mergeMyStocksDataToCache(parsedMessage)
  587. // 检查缓存中的数据是否已经完整
  588. if (this.isMyStocksCacheDataComplete()) {
  589. console.log('我的自选:缓存数据已完整,开始处理')
  590. const completeData = this.getCompleteMyStocksDataFromCache()
  591. this.processCompleteMyStocksData(completeData)
  592. } else {
  593. console.log('我的自选:缓存数据仍不完整,等待更多数据片段')
  594. }
  595. }
  596. }
  597. } catch (error) {
  598. console.error('我的自选:解析TCP股票数据失败:', error.message)
  599. console.error('我的自选:错误详情:', error)
  600. // 发生错误时清空所有缓存
  601. this.clearMyStocksStringFragmentCache()
  602. if (this.myStocksTcpDataCache.isCollecting) {
  603. console.log('我的自选:发生错误,清空对象级数据缓存')
  604. this.myStocksTcpDataCache.isCollecting = false
  605. this.myStocksTcpDataCache.expectedCount = 0
  606. this.myStocksTcpDataCache.collectedData = {}
  607. this.myStocksTcpDataCache.timestamp = ''
  608. this.myStocksTcpDataCache.type = ''
  609. }
  610. }
  611. },
  612. // 验证我的自选数据完整性
  613. validateMyStocksDataIntegrity(parsedMessage) {
  614. if (!parsedMessage.count || !parsedMessage.data) {
  615. return false
  616. }
  617. const dataObjectCount = Object.keys(parsedMessage.data).length
  618. const expectedCount = parsedMessage.count
  619. console.log(`我的自选数据完整性验证: 期望${expectedCount}个对象,实际${dataObjectCount}个对象`)
  620. return dataObjectCount === expectedCount
  621. },
  622. // 检查我的自选数据状态(完整数据、前半部分数据、后半部分数据)
  623. getMyStocksDataStatus(message) {
  624. if (typeof message !== 'string') {
  625. return 'invalid'
  626. }
  627. const trimmedMessage = message.trim()
  628. const startsWithBrace = trimmedMessage.startsWith('{')
  629. const endsWithBrace = trimmedMessage.endsWith('}')
  630. if (startsWithBrace && endsWithBrace) {
  631. // 以{开头,以}结尾 - 完整数据
  632. console.log('我的自选:检测到完整数据格式')
  633. return 'complete'
  634. } else if (startsWithBrace && !endsWithBrace) {
  635. // 以{开头,不以}结尾 - 前半部分数据
  636. console.log('我的自选:检测到前半部分数据')
  637. return 'first_part'
  638. } else if (!startsWithBrace && endsWithBrace) {
  639. // 不以{开头,以}结尾 - 后半部分数据
  640. console.log('我的自选:检测到后半部分数据')
  641. return 'second_part'
  642. } else {
  643. // 其他情况 - 无效数据
  644. console.log('我的自选:检测到无效数据格式')
  645. return 'invalid'
  646. }
  647. },
  648. // 缓存我的自选前半部分数据
  649. cacheMyStocksFirstPartData(message) {
  650. this.myStocksTcpDataCache.firstPartData = message.trim()
  651. this.myStocksTcpDataCache.isWaitingSecondPart = true
  652. console.log('我的自选:已缓存前半部分数据,长度:', this.myStocksTcpDataCache.firstPartData.length)
  653. },
  654. // 拼接我的自选前后两部分数据
  655. concatenateMyStocksDataParts(secondPartMessage) {
  656. const completeMessage = this.myStocksTcpDataCache.firstPartData + secondPartMessage.trim()
  657. console.log('我的自选:数据拼接完成,完整数据长度:', completeMessage.length)
  658. // 清空缓存
  659. this.clearMyStocksStringFragmentCache()
  660. return completeMessage
  661. },
  662. // 清空我的自选字符串片段缓存
  663. clearMyStocksStringFragmentCache() {
  664. this.myStocksTcpDataCache.firstPartData = ''
  665. this.myStocksTcpDataCache.isWaitingSecondPart = false
  666. console.log('我的自选:字符串片段缓存已清空')
  667. },
  668. // 合并我的自选数据到缓存中
  669. mergeMyStocksDataToCache(parsedMessage) {
  670. // 如果是第一次收集数据,初始化缓存
  671. if (!this.myStocksTcpDataCache.isCollecting) {
  672. this.myStocksTcpDataCache.isCollecting = true
  673. this.myStocksTcpDataCache.expectedCount = parsedMessage.count
  674. this.myStocksTcpDataCache.collectedData = {}
  675. this.myStocksTcpDataCache.timestamp = parsedMessage.timestamp
  676. this.myStocksTcpDataCache.type = parsedMessage.type
  677. console.log('我的自选:开始收集数据片段,期望总数:', parsedMessage.count)
  678. }
  679. // 合并新数据到缓存中
  680. if (parsedMessage.data) {
  681. Object.assign(this.myStocksTcpDataCache.collectedData, parsedMessage.data)
  682. console.log('我的自选:数据片段已合并,当前已收集:', Object.keys(this.myStocksTcpDataCache.collectedData).length, '个对象')
  683. }
  684. },
  685. // 检查我的自选缓存数据是否完整
  686. isMyStocksCacheDataComplete() {
  687. const collectedCount = Object.keys(this.myStocksTcpDataCache.collectedData).length
  688. const expectedCount = this.myStocksTcpDataCache.expectedCount
  689. console.log(`我的自选缓存数据检查: 已收集${collectedCount}个,期望${expectedCount}`)
  690. return collectedCount === expectedCount && collectedCount > 0
  691. },
  692. // 获取我的自选完整的缓存数据并清空缓存
  693. getCompleteMyStocksDataFromCache() {
  694. const completeData = {
  695. count: this.myStocksTcpDataCache.expectedCount,
  696. data: { ...this.myStocksTcpDataCache.collectedData },
  697. stock_count: this.myStocksTcpDataCache.expectedCount,
  698. timestamp: this.myStocksTcpDataCache.timestamp,
  699. type: this.myStocksTcpDataCache.type
  700. }
  701. // 清空缓存
  702. this.myStocksTcpDataCache.isCollecting = false
  703. this.myStocksTcpDataCache.expectedCount = 0
  704. this.myStocksTcpDataCache.collectedData = {}
  705. this.myStocksTcpDataCache.timestamp = ''
  706. this.myStocksTcpDataCache.type = ''
  707. console.log('我的自选:获取完整数据并清空缓存,数据对象数:', Object.keys(completeData.data).length)
  708. return completeData
  709. },
  710. // 处理我的自选完整的股票数据
  711. processCompleteMyStocksData(completeData) {
  712. console.log("我的自选:开始更新我的自选TCP股票数据存储")
  713. // 更新我的自选TCP股票数据存储
  714. this.myStocksTcpData = {
  715. count: completeData.count || 0,
  716. data: completeData.data || {},
  717. stock_count: completeData.stock_count || 0,
  718. timestamp: completeData.timestamp || '',
  719. type: completeData.type || ''
  720. }
  721. // 获取所有股票的数据用于显示
  722. const stockCodes = Object.keys(completeData.data)
  723. const newMyStocksInfoList = []
  724. // 只处理前3条股票数据
  725. const maxStocks = Math.min(3, stockCodes.length)
  726. for (let i = 0; i < maxStocks; i++) {
  727. const stockCode = stockCodes[i]
  728. // 检查数据结构
  729. if (completeData.data[stockCode] && Array.isArray(completeData.data[stockCode]) && completeData.data[stockCode].length > 0) {
  730. const stockData = completeData.data[stockCode][0] // 取第一条数据
  731. console.log('遍历的数据:', stockCode, stockData)
  732. if (stockData && stockData.current_price !== undefined) {
  733. // 直接使用返回的数据,不重新计算
  734. newMyStocksInfoList.push({
  735. stock_code: stockCode,
  736. stock_name: stockData.stock_name || '未知股票',
  737. current_price: parseFloat(stockData.current_price).toFixed(2),
  738. change: stockData.change || '0.00%',
  739. change_value: stockData.change_value || 0,
  740. change_percent: parseFloat(stockData.change) || 0
  741. })
  742. }
  743. }
  744. }
  745. // 更新我的自选股票信息列表
  746. if (newMyStocksInfoList.length > 0) {
  747. this.myStocksInfoList = newMyStocksInfoList
  748. console.log('我的自选:股票数据更新成功,共', newMyStocksInfoList.length, '个股票:', this.myStocksInfoList)
  749. }
  750. },
  751. // 发送TCP消息
  752. sendTcpMessage() {
  753. // 构造要发送的消息对象
  754. const messageData =
  755. // {
  756. // command: "real_time",
  757. // stock_code: "SH.000001"
  758. // }
  759. // {"command": "stock_list"}
  760. // {"command": "batch_real_time", "stock_codes": ["SH.000001"]}
  761. {"command": "batch_real_time", "stock_codes": ["SH.000001","SH.000002","SH.000003"]}
  762. // 发送消息
  763. const success = tcpConnection.send(messageData)
  764. if (success) {
  765. // console.log('home发送TCP消息:', messageData)
  766. uni.showToast({
  767. title: '服务器连接成功',
  768. icon: 'success',
  769. duration: 1500
  770. })
  771. }
  772. },
  773. // 发送我的自选TCP消息
  774. sendMyStocksTcpMessage() {
  775. // 构造要发送的消息对象 - 我的自选股票数据请求
  776. const messageData = {"command": "batch_real_time", "stock_codes": ["SH.000001","SH.000002","SH.000003"]}
  777. // 发送消息到channel 2(我的自选TCP连接)
  778. const success = tcpConnection.send(messageData, { channel: '2' })
  779. if (success) {
  780. console.log('我的自选:发送TCP消息成功:', messageData)
  781. uni.showToast({
  782. title: '我的自选数据请求已发送',
  783. icon: 'success',
  784. duration: 1500
  785. })
  786. } else {
  787. console.error('我的自选:发送TCP消息失败')
  788. uni.showToast({
  789. title: '我的自选连接失败',
  790. icon: 'error',
  791. duration: 1500
  792. })
  793. }
  794. },
  795. // 清空消息记录
  796. clearTcpMessages() {
  797. this.tcpMessages = []
  798. uni.showToast({
  799. title: '消息记录已清空',
  800. icon: 'success',
  801. duration: 1500
  802. })
  803. },
  804. // 获取TCP连接状态
  805. getTcpStatus() {
  806. const status = tcpConnection.getConnectionStatus()
  807. uni.showModal({
  808. title: 'TCP连接状态',
  809. content: `当前状态: ${status ? '已连接' : '未连接'}\n消息数量: ${this.tcpMessages.length}`,
  810. showCancel: false
  811. })
  812. },
  813. // 验证数据完整性(检查count值与data对象个数是否相等)
  814. validateDataIntegrity(parsedMessage) {
  815. if (!parsedMessage.count || !parsedMessage.data) {
  816. return false
  817. }
  818. const dataObjectCount = Object.keys(parsedMessage.data).length
  819. const expectedCount = parsedMessage.count
  820. // console.log(`数据完整性验证: 期望${expectedCount}个对象,实际${dataObjectCount}个对象`)
  821. return dataObjectCount === expectedCount
  822. },
  823. // 检查数据状态(完整数据、前半部分数据、后半部分数据)
  824. getDataStatus(message) {
  825. if (typeof message !== 'string') {
  826. return 'invalid'
  827. }
  828. const trimmedMessage = message.trim()
  829. const startsWithBrace = trimmedMessage.startsWith('{')
  830. const endsWithBrace = trimmedMessage.endsWith('}')
  831. if (startsWithBrace && endsWithBrace) {
  832. // 以{开头,以}结尾 - 完整数据
  833. // console.log('检测到完整数据格式')
  834. return 'complete'
  835. } else if (startsWithBrace && !endsWithBrace) {
  836. // 以{开头,不以}结尾 - 前半部分数据
  837. // console.log('检测到前半部分数据')
  838. return 'first_part'
  839. } else if (!startsWithBrace && endsWithBrace) {
  840. // 不以{开头,以}结尾 - 后半部分数据
  841. // console.log('检测到后半部分数据')
  842. return 'second_part'
  843. } else {
  844. // 其他情况 - 无效数据
  845. // console.log('检测到无效数据格式')
  846. return 'invalid'
  847. }
  848. },
  849. // 缓存前半部分数据
  850. cacheFirstPartData(message) {
  851. this.tcpDataCache.firstPartData = message.trim()
  852. this.tcpDataCache.isWaitingSecondPart = true
  853. // console.log('已缓存前半部分数据,长度:', this.tcpDataCache.firstPartData.length)
  854. },
  855. // 拼接前后两部分数据
  856. concatenateDataParts(secondPartMessage) {
  857. const completeMessage = this.tcpDataCache.firstPartData + secondPartMessage.trim()
  858. // console.log('数据拼接完成,完整数据长度:', completeMessage.length)
  859. // 清空缓存
  860. this.clearStringFragmentCache()
  861. return completeMessage
  862. },
  863. // 清空字符串片段缓存
  864. clearStringFragmentCache() {
  865. this.tcpDataCache.firstPartData = ''
  866. this.tcpDataCache.isWaitingSecondPart = false
  867. // console.log('字符串片段缓存已清空')
  868. },
  869. // 合并数据到缓存中
  870. mergeDataToCache(parsedMessage) {
  871. // 如果是第一次收集数据,初始化缓存
  872. if (!this.tcpDataCache.isCollecting) {
  873. this.tcpDataCache.isCollecting = true
  874. this.tcpDataCache.expectedCount = parsedMessage.count
  875. this.tcpDataCache.collectedData = {}
  876. this.tcpDataCache.timestamp = parsedMessage.timestamp
  877. this.tcpDataCache.type = parsedMessage.type
  878. // console.log('开始收集数据片段,期望总数:', parsedMessage.count)
  879. }
  880. // 合并新数据到缓存中
  881. if (parsedMessage.data) {
  882. Object.assign(this.tcpDataCache.collectedData, parsedMessage.data)
  883. // console.log('数据片段已合并,当前已收集:', Object.keys(this.tcpDataCache.collectedData).length, '个对象')
  884. }
  885. },
  886. // 检查缓存数据是否完整
  887. isCacheDataComplete() {
  888. const collectedCount = Object.keys(this.tcpDataCache.collectedData).length
  889. const expectedCount = this.tcpDataCache.expectedCount
  890. // console.log(`缓存数据检查: 已收集${collectedCount}个,期望${expectedCount}个`)
  891. return collectedCount === expectedCount && collectedCount > 0
  892. },
  893. // 获取完整的缓存数据并清空缓存
  894. getCompleteDataFromCache() {
  895. const completeData = {
  896. count: this.tcpDataCache.expectedCount,
  897. data: { ...this.tcpDataCache.collectedData },
  898. stock_count: this.tcpDataCache.expectedCount,
  899. timestamp: this.tcpDataCache.timestamp,
  900. type: this.tcpDataCache.type
  901. }
  902. // 清空缓存
  903. this.tcpDataCache.isCollecting = false
  904. this.tcpDataCache.expectedCount = 0
  905. this.tcpDataCache.collectedData = {}
  906. this.tcpDataCache.timestamp = ''
  907. this.tcpDataCache.type = ''
  908. // console.log('获取完整数据并清空缓存,数据对象数:', Object.keys(completeData.data).length)
  909. return completeData
  910. },
  911. // 解析TCP股票数据
  912. parseStockData(message) {
  913. try {
  914. // console.log('进入parseStockData, message类型:', typeof message, '长度:', message.length)
  915. // 第一步:检查数据状态(完整、前半部分、后半部分)
  916. const dataStatus = this.getDataStatus(message)
  917. let completeMessage = ''
  918. switch (dataStatus) {
  919. case 'complete':
  920. // 完整数据,直接处理
  921. // console.log('检测到完整数据,直接处理')
  922. completeMessage = message
  923. break
  924. case 'first_part':
  925. // 前半部分数据,缓存并等待后半部分
  926. // console.log('检测到前半部分数据,开始缓存')
  927. this.cacheFirstPartData(message)
  928. return // 等待后半部分数据
  929. case 'second_part':
  930. // 后半部分数据,检查是否有缓存的前半部分
  931. if (this.tcpDataCache.isWaitingSecondPart) {
  932. // console.log('检测到后半部分数据,开始拼接')
  933. completeMessage = this.concatenateDataParts(message)
  934. } else {
  935. // console.log('收到后半部分数据,但没有缓存的前半部分,跳过处理')
  936. return
  937. }
  938. break
  939. case 'invalid':
  940. default:
  941. // console.log('数据格式无效,跳过处理')
  942. return
  943. }
  944. // 第二步:解析完整的JSON数据
  945. let parsedMessage
  946. try {
  947. // console.log('开始解析完整JSON数据,长度:', completeMessage.length)
  948. parsedMessage = JSON.parse(completeMessage)
  949. // console.log('JSON解析成功,解析后类型:', typeof parsedMessage, parsedMessage)
  950. } catch (parseError) {
  951. // console.error('JSON解析失败:', parseError.message)
  952. // 清空字符串片段缓存
  953. this.clearStringFragmentCache()
  954. return
  955. }
  956. // 第三步:检查是否是股票数据类型
  957. if (!((parsedMessage.type === 'batch_data_chunk' || parsedMessage.type === 'batch_realtime_data') && parsedMessage.data)) {
  958. // console.log('不是batch_data_chunk或batch_realtime_data类型的消息,跳过处理')
  959. return
  960. }
  961. // console.log('开始处理股票数据')
  962. // 第四步:验证数据完整性(对象级别的完整性检查)
  963. // 注意:batch_data_chunk类型的数据不需要验证完整性,直接处理
  964. if (parsedMessage.type === 'batch_data_chunk') {
  965. // console.log('batch_data_chunk类型数据,跳过完整性验证,直接处理')
  966. this.processCompleteStockData(parsedMessage)
  967. } else {
  968. const isDataComplete = this.validateDataIntegrity(parsedMessage)
  969. if (isDataComplete) {
  970. // 数据完整,直接处理
  971. // console.log('对象级数据完整,直接处理')
  972. this.processCompleteStockData(parsedMessage)
  973. } else {
  974. // 数据不完整,需要拼接(对象级别的拼接)
  975. // console.log('对象级数据不完整,开始拼接处理')
  976. // 将当前数据合并到缓存中
  977. this.mergeDataToCache(parsedMessage)
  978. // 检查缓存中的数据是否已经完整
  979. if (this.isCacheDataComplete()) {
  980. // console.log('缓存数据已完整,开始处理')
  981. const completeData = this.getCompleteDataFromCache()
  982. this.processCompleteStockData(completeData)
  983. } else {
  984. // console.log('缓存数据仍不完整,等待更多数据片段')
  985. }
  986. }
  987. }
  988. } catch (error) {
  989. // console.error('解析TCP股票数据失败:', error.message)
  990. // console.error('错误详情:', error)
  991. // 发生错误时清空所有缓存
  992. this.clearStringFragmentCache()
  993. if (this.tcpDataCache.isCollecting) {
  994. // console.log('发生错误,清空对象级数据缓存')
  995. this.tcpDataCache.isCollecting = false
  996. this.tcpDataCache.expectedCount = 0
  997. this.tcpDataCache.collectedData = {}
  998. this.tcpDataCache.timestamp = ''
  999. this.tcpDataCache.type = ''
  1000. }
  1001. }
  1002. },
  1003. // 处理完整的股票数据
  1004. processCompleteStockData(completeData) {
  1005. // console.log("开始更新TCP股票数据存储")
  1006. // 更新TCP股票数据存储
  1007. this.tcpStockData = {
  1008. count: completeData.count || 0,
  1009. data: completeData.data || {},
  1010. stock_count: completeData.stock_count || 0,
  1011. timestamp: completeData.timestamp || '',
  1012. type: completeData.type || ''
  1013. }
  1014. // 获取所有股票的数据用于显示(最多3个)
  1015. const stockCodes = Object.keys(completeData.data)
  1016. const newStockInfoList = []
  1017. // 处理最多3个股票数据
  1018. for (let i = 0; i < Math.min(stockCodes.length, 3); i++) {
  1019. const stockCode = stockCodes[i]
  1020. // 检查数据结构
  1021. if (completeData.data[stockCode] && Array.isArray(completeData.data[stockCode]) && completeData.data[stockCode].length > 0) {
  1022. const stockData = completeData.data[stockCode][0] // 取第一条数据
  1023. if (stockData && stockData.current_price !== undefined && stockData.pre_close !== undefined) {
  1024. // 计算涨跌幅
  1025. const changeValue = stockData.current_price - stockData.pre_close
  1026. const changePercent = ((changeValue / stockData.pre_close) * 100).toFixed(2)
  1027. const changeSign = changeValue >= 0 ? '+' : ''
  1028. // 添加到股票信息列表
  1029. newStockInfoList.push({
  1030. stock_name: stockData.stock_name || '未知股票',
  1031. current_price: stockData.current_price ? stockData.current_price.toFixed(2) : '0.00',
  1032. change: `${changeSign}${changePercent}%`,
  1033. change_value: changeValue,
  1034. change_percent: parseFloat(changePercent)
  1035. })
  1036. }
  1037. }
  1038. }
  1039. // 更新当前显示的股票信息列表
  1040. if (newStockInfoList.length > 0) {
  1041. this.currentStockInfoList = newStockInfoList
  1042. // console.log('股票数据更新成功,共', newStockInfoList.length, '个股票:', this.currentStockInfoList)
  1043. }
  1044. },
  1045. // 移除TCP监听器
  1046. removeTcpListeners() {
  1047. if (this.connectionListener) {
  1048. tcpConnection.removeConnectionListener(this.connectionListener)
  1049. this.connectionListener = null
  1050. // console.log('已移除TCP连接状态监听器')
  1051. }
  1052. if (this.messageListener) {
  1053. tcpConnection.removeMessageListener(this.messageListener)
  1054. this.messageListener = null
  1055. // console.log('已移除TCP消息监听器')
  1056. }
  1057. }
  1058. }
  1059. }
  1060. </script>
  1061. <style scoped>
  1062. .main {
  1063. display: flex;
  1064. flex-direction: column;
  1065. height: 100vh;
  1066. background-color: #ffffff;
  1067. }
  1068. .header {
  1069. display: flex;
  1070. justify-content: space-between;
  1071. align-items: center;
  1072. padding: 10px 15px;
  1073. background-color: #ffffff;
  1074. }
  1075. .title {
  1076. font-size: 22px;
  1077. font-weight: bold;
  1078. text-align: center;
  1079. flex: 1;
  1080. }
  1081. .headphone-icon, .notification-icon {
  1082. width: 40px;
  1083. display: flex;
  1084. align-items: center;
  1085. justify-content: center;
  1086. }
  1087. .header-icon-image {
  1088. width: 24px;
  1089. height: 24px;
  1090. object-fit: contain;
  1091. }
  1092. .content-container {
  1093. flex: 1;
  1094. padding: 10px;
  1095. width: 100%;
  1096. box-sizing: border-box;
  1097. overflow-x: hidden;
  1098. }
  1099. .section {
  1100. margin-bottom: 15px;
  1101. /* background-color: #f5f5f5; */
  1102. background-color: #ffffff;
  1103. border-radius: 8px;
  1104. padding: 15px;
  1105. }
  1106. .section-header {
  1107. display: flex;
  1108. justify-content: space-between;
  1109. align-items: center;
  1110. margin-bottom: 15px;
  1111. padding-bottom: 10px;
  1112. }
  1113. .section-title {
  1114. font-size: 18px;
  1115. font-weight: bold;
  1116. color: #000000;
  1117. }
  1118. .section-title-text{
  1119. font-size: 14px;
  1120. }
  1121. .text-gap{
  1122. height: 10px;
  1123. }
  1124. .more-btn {
  1125. font-size: 12px;
  1126. font-weight: bold;
  1127. color: #ffffff;
  1128. background-color: #000000;
  1129. padding: 4px 8px;
  1130. border-radius: 4px;
  1131. }
  1132. /* 市场概览样式 */
  1133. .market-header {
  1134. display: flex;
  1135. justify-content: space-between;
  1136. align-items: center;
  1137. padding: 10px 15px;
  1138. background-color: #ffffff;
  1139. margin-bottom: 10px;
  1140. }
  1141. .market-content {
  1142. background-color: #f5f5f5;
  1143. border-radius: 8px;
  1144. padding: 15px;
  1145. margin: 0 15px;
  1146. }
  1147. .market-image {
  1148. margin-bottom: 15px;
  1149. display: flex;
  1150. justify-content: center;
  1151. }
  1152. .overview-image {
  1153. width: 100%;
  1154. border-radius: 8px;
  1155. }
  1156. .market-data {
  1157. display: flex;
  1158. flex-direction: column;
  1159. gap: 10px;
  1160. }
  1161. /* 间隔样式 */
  1162. .section-gap {
  1163. height: 20px;
  1164. }
  1165. .market-item {
  1166. display: flex;
  1167. justify-content: space-between;
  1168. padding: 8px 0;
  1169. border-bottom: 1px solid #f0f0f0;
  1170. }
  1171. .down {
  1172. color: #ff4d4f;
  1173. }
  1174. .up {
  1175. color: #52c41a;
  1176. }
  1177. /* DeepMate样式 */
  1178. .deepmate-container {
  1179. background-color: #ffe6e6;
  1180. border-radius: 10px;
  1181. padding: 15px;
  1182. margin: 0 15px;
  1183. }
  1184. .deepmate-header {
  1185. margin-bottom: 10px;
  1186. }
  1187. .title-container {
  1188. display: flex;
  1189. align-items: center;
  1190. justify-content: space-between;
  1191. width: 100%;
  1192. }
  1193. .title-left {
  1194. width: 50%;
  1195. }
  1196. .title-right {
  1197. width: 50%;
  1198. display: flex;
  1199. justify-content: flex-end;
  1200. }
  1201. .deepmate-title {
  1202. font-size: 18px;
  1203. font-weight: bold;
  1204. color: #ff4d4f;
  1205. }
  1206. .deepmate-icon {
  1207. width: 60px;
  1208. height: 60px;
  1209. margin-left: 0;
  1210. }
  1211. .deepmate-subtitle {
  1212. font-size: 12px;
  1213. color: #666;
  1214. margin-left: 5px;
  1215. }
  1216. .deepmate-hotspots {
  1217. margin: 10px 0;
  1218. }
  1219. .hotspot-item {
  1220. background-color: #f5f5f5;
  1221. padding: 8px 12px;
  1222. border-radius: 6px;
  1223. margin-bottom: 8px;
  1224. }
  1225. .hotspot-item text {
  1226. font-size: 14px;
  1227. color: #333;
  1228. }
  1229. .deepmate-action {
  1230. display: flex;
  1231. justify-content: center;
  1232. align-items: center;
  1233. background-color: #ffffff;
  1234. border-radius: 20px;
  1235. padding: 10px;
  1236. margin-top: 10px;
  1237. }
  1238. /* 欢迎部分样式 */
  1239. .welcome-section {
  1240. margin-bottom: 15px;
  1241. padding: 0;
  1242. }
  1243. .welcome-swiper {
  1244. width: 100%;
  1245. height: 150px;
  1246. border-radius: 0;
  1247. overflow: hidden;
  1248. }
  1249. .deepmate-section {
  1250. padding: 0;
  1251. }
  1252. .swiper-image {
  1253. width: 100%;
  1254. height: 100%;
  1255. border-radius: 8px;
  1256. object-fit: contain;
  1257. }
  1258. /* 深度探索样式 */
  1259. .deep-exploration {
  1260. margin-top: 15px;
  1261. padding: 0; /* 移除内边距,让子容器自己控制 */
  1262. }
  1263. .section-header-container {
  1264. margin-bottom: 10px;
  1265. }
  1266. .section-header {
  1267. display: flex;
  1268. justify-content: space-between;
  1269. align-items: center;
  1270. padding: 10px 15px;
  1271. background-color: #ffffff;
  1272. }
  1273. .header-left {
  1274. display: flex;
  1275. align-items: center;
  1276. }
  1277. .header-right {
  1278. display: flex;
  1279. align-items: center;
  1280. }
  1281. .section-title {
  1282. font-size: 16px;
  1283. font-weight: bold;
  1284. color: #333;
  1285. }
  1286. .more-btn {
  1287. font-size: 12px;
  1288. color: #ffffff;
  1289. }
  1290. .exploration-container {
  1291. border-radius: 8px;
  1292. overflow: hidden;
  1293. }
  1294. .exploration-content {
  1295. display: flex;
  1296. justify-content: space-between;
  1297. padding: 15px;
  1298. background-color: #f5f5f5;
  1299. border-radius: 8px;
  1300. }
  1301. .exploration-item {
  1302. display: flex;
  1303. flex-direction: column;
  1304. align-items: center;
  1305. width: 22%;
  1306. background-color: #ffffff;
  1307. border-radius: 8px;
  1308. padding: 10px 0;
  1309. }
  1310. .exploration-icon {
  1311. width: 50px;
  1312. height: 50px;
  1313. margin-bottom: 8px;
  1314. }
  1315. .exploration-text {
  1316. font-size: 12px;
  1317. color: #333;
  1318. }
  1319. .icon-text {
  1320. font-size: 12px;
  1321. }
  1322. /* 我的自选样式 */
  1323. .my-selection {
  1324. padding: 0; /* 移除内边距,让子容器自己控制 */
  1325. }
  1326. .stock-container {
  1327. border-radius: 8px;
  1328. overflow: hidden;
  1329. background-color: #f5f5f5;
  1330. padding: 15px;
  1331. box-sizing: border-box;
  1332. }
  1333. .stock-list {
  1334. display: flex;
  1335. flex-direction: row;
  1336. justify-content: center;
  1337. background-color: #f8f8f8;
  1338. border-radius: 8px;
  1339. padding: 15px;
  1340. gap: 10px; /* 添加卡片之间的间距 */
  1341. }
  1342. .stock-item {
  1343. display: flex;
  1344. flex-direction: column;
  1345. justify-content: space-between;
  1346. padding: 3px;
  1347. background-color: #ffffff;
  1348. border-radius: 8px;
  1349. width: 30%;
  1350. box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  1351. will-change: transform;
  1352. transform: translateZ(0);
  1353. }
  1354. .stock-info {
  1355. display: flex;
  1356. flex-direction: column;
  1357. align-items: center;
  1358. margin-bottom: 5px;
  1359. }
  1360. .stock-chart {
  1361. display: flex;
  1362. align-items: center;
  1363. justify-content: center;
  1364. }
  1365. .name-container {
  1366. display: flex;
  1367. align-items: center;
  1368. gap: 5px;
  1369. }
  1370. .stock-name {
  1371. font-size: 12px;
  1372. font-weight: bold;
  1373. color: #333;
  1374. }
  1375. .stock-code-label {
  1376. font-size: 12px;
  1377. color: #666;
  1378. background-color: #f5f5f5;
  1379. padding: 0 4px;
  1380. border-radius: 3px;
  1381. }
  1382. .stock-price {
  1383. font-size: 12px;
  1384. color: #666;
  1385. }
  1386. .price-container {
  1387. display: flex;
  1388. align-items: center;
  1389. gap: 5px;
  1390. }
  1391. .stock-change {
  1392. font-size: 12px;
  1393. }
  1394. .stock-up {
  1395. color: #4cd964;
  1396. }
  1397. .stock-down {
  1398. color: #ff3b30;
  1399. }
  1400. .chart-image {
  1401. width: 100px;
  1402. height: 40px;
  1403. }
  1404. /* 市场看点样式 */
  1405. .highlights-title-container {
  1406. background-color: #ffffff;
  1407. margin-bottom: 10px;
  1408. }
  1409. .highlights-image-container {
  1410. background-color: #f5f5f5;
  1411. border-radius: 8px;
  1412. padding: 10px;
  1413. margin-bottom: 15px;
  1414. }
  1415. .highlights-image {
  1416. width: 100%;
  1417. height: 150px;
  1418. border-radius: 4px;
  1419. }
  1420. /* 机构动向简报样式 */
  1421. .institutional-reports {
  1422. margin-top: 15px;
  1423. background-color: #f8f8f8;
  1424. border-radius: 8px;
  1425. padding: 10px;
  1426. }
  1427. .section-title-container {
  1428. position: relative;
  1429. padding-left: 10px;
  1430. margin-bottom: 5px;
  1431. }
  1432. .section-title-container:before {
  1433. content: "";
  1434. position: absolute;
  1435. left: 0;
  1436. top: 0;
  1437. bottom: 0;
  1438. width: 4px;
  1439. background-color: #ff4d4f;
  1440. border-radius: 2px;
  1441. }
  1442. .report-item {
  1443. background-color: #ffffff;
  1444. border-radius: 8px;
  1445. padding: 10px;
  1446. margin-bottom: 8px;
  1447. }
  1448. .report-stock {
  1449. font-size: 14px;
  1450. font-weight: bold;
  1451. color: #e74c3c;
  1452. margin-bottom: 5px;
  1453. }
  1454. .report-status {
  1455. font-size: 12px;
  1456. color: #333;
  1457. }
  1458. .view-more {
  1459. text-align: center;
  1460. color: #1890ff;
  1461. font-size: 12px;
  1462. padding: 5px;
  1463. }
  1464. /* 底部空间 */
  1465. .bottom-space {
  1466. height: 60px;
  1467. }
  1468. /* 底部导航 */
  1469. .static-footer {
  1470. position: fixed;
  1471. bottom: 0;
  1472. width: 100%;
  1473. }
  1474. /* TCP测试区域样式 */
  1475. .tcp-test-section {
  1476. border: 1px solid #e0e0e0;
  1477. background-color: #f9f9f9;
  1478. }
  1479. .tcp-status {
  1480. padding: 4px 12px;
  1481. border-radius: 12px;
  1482. font-size: 12px;
  1483. }
  1484. .tcp-status.connected {
  1485. background-color: #e8f5e8;
  1486. color: #4caf50;
  1487. border: 1px solid #4caf50;
  1488. }
  1489. .tcp-status.disconnected {
  1490. background-color: #ffeaea;
  1491. color: #f44336;
  1492. border: 1px solid #f44336;
  1493. }
  1494. .status-text {
  1495. font-size: 12px;
  1496. font-weight: bold;
  1497. }
  1498. .tcp-controls {
  1499. display: flex;
  1500. flex-wrap: wrap;
  1501. gap: 8px;
  1502. margin-bottom: 15px;
  1503. }
  1504. .tcp-btn {
  1505. flex: 1;
  1506. min-width: 80px;
  1507. height: 36px;
  1508. border-radius: 6px;
  1509. font-size: 12px;
  1510. border: none;
  1511. color: white;
  1512. font-weight: bold;
  1513. }
  1514. .connect-btn {
  1515. background-color: #4caf50;
  1516. }
  1517. .connect-btn:disabled {
  1518. background-color: #cccccc;
  1519. }
  1520. .disconnect-btn {
  1521. background-color: #f44336;
  1522. }
  1523. .disconnect-btn:disabled {
  1524. background-color: #cccccc;
  1525. }
  1526. .send-btn {
  1527. background-color: #2196f3;
  1528. }
  1529. .send-btn:disabled {
  1530. background-color: #cccccc;
  1531. }
  1532. .status-btn {
  1533. background-color: #ff9800;
  1534. }
  1535. .tcp-messages {
  1536. margin-top: 15px;
  1537. border-top: 1px solid #e0e0e0;
  1538. padding-top: 15px;
  1539. }
  1540. .messages-header {
  1541. display: flex;
  1542. justify-content: space-between;
  1543. align-items: center;
  1544. margin-bottom: 10px;
  1545. }
  1546. .messages-title {
  1547. font-size: 14px;
  1548. font-weight: bold;
  1549. color: #333;
  1550. }
  1551. .clear-btn {
  1552. padding: 4px 8px;
  1553. background-color: #f44336;
  1554. color: white;
  1555. border: none;
  1556. border-radius: 4px;
  1557. font-size: 10px;
  1558. }
  1559. .messages-list {
  1560. max-height: 200px;
  1561. border: 1px solid #e0e0e0;
  1562. border-radius: 6px;
  1563. padding: 8px;
  1564. background-color: white;
  1565. }
  1566. .message-item {
  1567. margin-bottom: 8px;
  1568. padding: 8px;
  1569. border-radius: 6px;
  1570. border-left: 3px solid #ccc;
  1571. }
  1572. .message-item.sent {
  1573. background-color: #e3f2fd;
  1574. border-left-color: #2196f3;
  1575. }
  1576. .message-item.received {
  1577. background-color: #f1f8e9;
  1578. border-left-color: #4caf50;
  1579. }
  1580. .message-info {
  1581. display: flex;
  1582. justify-content: space-between;
  1583. margin-bottom: 4px;
  1584. }
  1585. .message-direction {
  1586. font-size: 10px;
  1587. font-weight: bold;
  1588. color: #666;
  1589. }
  1590. .message-time {
  1591. font-size: 10px;
  1592. color: #999;
  1593. }
  1594. .message-content {
  1595. font-size: 12px;
  1596. color: #333;
  1597. word-break: break-all;
  1598. }
  1599. /* 我的自选TCP控制样式 */
  1600. .my-stocks-tcp-control {
  1601. display: flex;
  1602. align-items: center;
  1603. justify-content: space-between;
  1604. margin-top: 8px;
  1605. padding: 8px 12px;
  1606. background-color: #f8f9fa;
  1607. border-radius: 8px;
  1608. border: 1px solid #e9ecef;
  1609. }
  1610. .tcp-buttons {
  1611. display: flex;
  1612. gap: 6px;
  1613. }
  1614. .my-stocks-tcp-control .tcp-btn {
  1615. flex: none;
  1616. min-width: 50px;
  1617. height: 28px;
  1618. border-radius: 4px;
  1619. font-size: 11px;
  1620. border: none;
  1621. color: white;
  1622. font-weight: bold;
  1623. }
  1624. </style>