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.

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