Browse Source

今日市场概览连接tcp成功;先断开再连接;

zhaowenkang/feature-20251028181547-行情页面
宋杰 4 weeks ago
parent
commit
376d8b559b
  1. 21
      api/tcpConnection.js
  2. 50
      components/MarketOverview.vue
  3. 705
      pages/home/home.vue

21
api/tcpConnection.js

@ -30,6 +30,26 @@ class TCPConnection {
* @param {Function} callback - 连接状态回调函数 * @param {Function} callback - 连接状态回调函数
*/ */
connect(config = {}, callback = null) { connect(config = {}, callback = null) {
// 如果已经连接,先断开现有连接
if (this.isConnected) {
console.log('检测到现有TCP连接,先断开...');
this.disconnect(config);
// 等待断开完成后再连接
setTimeout(() => {
this._performConnect(config, callback);
}, 300);
} else {
// 直接连接
this._performConnect(config, callback);
}
}
/**
* 执行TCP连接
* @param {Object} config - 连接配置
* @param {Function} callback - 连接状态回调函数
*/
_performConnect(config = {}, callback = null) {
const connectionConfig = { const connectionConfig = {
channel: config.channel || TCP_CONFIG.channel, channel: config.channel || TCP_CONFIG.channel,
ip: config.ip || TCP_CONFIG.ip, ip: config.ip || TCP_CONFIG.ip,
@ -41,6 +61,7 @@ class TCPConnection {
connectionConfig.charsetname = config.charsetname || TCP_CONFIG.charsetname; connectionConfig.charsetname = config.charsetname || TCP_CONFIG.charsetname;
} }
console.log('开始建立TCP连接:', connectionConfig);
TCPSocket.connect( TCPSocket.connect(
connectionConfig, connectionConfig,
result => { result => {

50
components/MarketOverview.vue

@ -18,20 +18,13 @@
<view class="forex-market"> <view class="forex-market">
<!-- 上部分三个汇率容器 --> <!-- 上部分三个汇率容器 -->
<view class="forex-rates"> <view class="forex-rates">
<view class="forex-rate-item up">
<text class="forex-pair">美元/日元</text>
<text class="forex-value">151.13</text>
<text class="forex-change">+1.62%</text>
</view>
<view class="forex-rate-item down">
<text class="forex-pair">美元/韩元</text>
<text class="forex-value">1424.900</text>
<text class="forex-change">-2.92%</text>
</view>
<view class="forex-rate-item up">
<text class="forex-pair">美元/英镑</text>
<text class="forex-value">0.730</text>
<text class="forex-change">+2.92%</text>
<view class="forex-rate-item"
v-for="(stock, index) in stockInfoList"
:key="index"
:class="stock.change_percent >= 0 ? 'up' : 'down'">
<text class="forex-pair">{{ stock.stock_name }}</text>
<text class="forex-value">{{ stock.current_price }}</text>
<text class="forex-change">{{ stock.change }}</text>
</view> </view>
</view> </view>
@ -94,6 +87,35 @@
<script> <script>
export default { export default {
name: 'MarketOverview', name: 'MarketOverview',
props: {
// 3
stockInfoList: {
type: Array,
default: () => ([
{
stock_name: '美元/日元',
current_price: '151.13',
change: '+1.62%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/韩元',
current_price: '1424.900',
change: '-2.92%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/英镑',
current_price: '0.730',
change: '+2.92%',
change_value: 0,
change_percent: 0
}
])
}
},
data() { data() {
return { return {
selectedMarket: 'forex', selectedMarket: 'forex',

705
pages/home/home.vue

@ -17,7 +17,7 @@
<!-- 内容区域 - 使用滚动视图 --> <!-- 内容区域 - 使用滚动视图 -->
<scroll-view scroll-y class="content-container"> <scroll-view scroll-y class="content-container">
<!-- 1. 今日市场概览 --> <!-- 1. 今日市场概览 -->
<market-overview></market-overview>
<market-overview :stock-info-list="currentStockInfoList"></market-overview>
<!-- 间隔 --> <!-- 间隔 -->
<view class="section-gap"></view> <view class="section-gap"></view>
@ -122,7 +122,43 @@
</view> </view>
</view> </view>
<!-- 5. 今日市场看点 -->
<!-- 5. TCP连接测试 -->
<!-- <view class="section tcp-test-section">
<view class="section-header">
<text class="section-title">TCP连接测试</text>
<view class="tcp-status" :class="{'connected': tcpConnected, 'disconnected': !tcpConnected}">
<text class="status-text">{{tcpConnected ? '已连接' : '未连接'}}</text>
</view>
</view> -->
<!-- TCP控制按钮 -->
<!-- <view class="tcp-controls">
<button class="tcp-btn connect-btn" @click="connectTcp" :disabled="tcpConnected">连接</button>
<button class="tcp-btn disconnect-btn" @click="disconnectTcp" :disabled="!tcpConnected">断开</button>
<button class="tcp-btn send-btn" @click="sendTcpMessage" :disabled="!tcpConnected">发送测试消息</button>
<button class="tcp-btn status-btn" @click="getTcpStatus">查看状态</button>
</view> -->
<!-- 消息记录 -->
<!-- <view class="tcp-messages" v-if="tcpMessages.length > 0">
<view class="messages-header">
<text class="messages-title">消息记录 ({{tcpMessages.length}})</text>
<button class="clear-btn" @click="clearTcpMessages">清空</button>
</view>
<scroll-view scroll-y class="messages-list" scroll-top="{{tcpMessages.length * 50}}">
<view class="message-item" v-for="(msg, index) in tcpMessages" :key="index"
:class="{'sent': msg.direction === 'sent', 'received': msg.direction === 'received'}">
<view class="message-info">
<text class="message-direction">{{msg.direction === 'sent' ? '发送' : '接收'}}</text>
<text class="message-time">{{msg.timestamp}}</text>
</view>
<text class="message-content">{{msg.content}}</text>
</view>
</scroll-view>
</view>
</view> -->
<!-- 6. 今日市场看点 -->
<view class="section-header highlights-title-container"> <view class="section-header highlights-title-container">
<text class="section-title">今日市场核心看点</text> <text class="section-title">今日市场核心看点</text>
</view> </view>
@ -143,6 +179,8 @@
import footerBar from '../../components/footerBar.vue' import footerBar from '../../components/footerBar.vue'
import MarketOverview from '../../components/MarketOverview.vue' import MarketOverview from '../../components/MarketOverview.vue'
import DeepMate from '../../components/DeepMate.vue' import DeepMate from '../../components/DeepMate.vue'
import tcpConnection from '../../api/tcpConnection.js'
import th from '../../static/language/th'
export default { export default {
components: { components: {
@ -178,7 +216,62 @@ export default {
], ],
// //
debounceTimer: null
debounceTimer: null,
// TCP
tcpConnected: false,
tcpMessages: [],
lastSentMessage: '',
// TCP
connectionListener: null,
messageListener: null,
// TCP
tcpStockData: {
count: 0,
data: {},
stock_count: 0,
timestamp: '',
type: ''
},
// TCP
tcpDataCache: {
isCollecting: false, //
expectedCount: 0, //
collectedData: {}, //
timestamp: '', //
type: '', //
//
firstPartData: '', //
isWaitingSecondPart: false //
},
// 3MarketOverview
currentStockInfoList: [
{
stock_name: '美元/日元',
current_price: '151.13',
change: '+1.62%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/韩元',
current_price: '1424.900',
change: '-2.92%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/英镑',
current_price: '0.730',
change: '+2.92%',
change_value: 0,
change_percent: 0
}
]
} }
}, },
@ -201,6 +294,34 @@ export default {
} }
}) })
}) })
// TCP
this.initTcpListeners()
// TCP
this.$nextTick(() => {
console.log('页面渲染完成,开始自动连接TCP服务器...')
this.connectTcp()
})
},
//
onUnload() {
// TCP
if (this.tcpConnected) {
console.log('页面销毁,自动关闭TCP连接')
tcpConnection.disconnect()
this.tcpConnected = false
}
//
if (this.debounceTimer) {
clearTimeout(this.debounceTimer)
this.debounceTimer = null
}
// TCP
this.removeTcpListeners()
}, },
methods: { methods: {
@ -223,6 +344,433 @@ export default {
fn() fn()
this.debounceTimer = null this.debounceTimer = null
}, delay) }, delay)
},
// TCP
// TCP
initTcpListeners() {
//
this.connectionListener = (status, result) => {
this.tcpConnected = (status === 'connected')
console.log('TCP连接状态变化:', status, this.tcpConnected)
//
uni.showToast({
title: status === 'connected' ? '连接服务器成功' : '服务器连接断开',
icon: status === 'connected' ? 'success' : 'none',
duration: 1000
})
//
if (status === 'connected') {
console.log('TCP连接成功,自动发送股票数据请求...')
//
setTimeout(() => {
this.sendTcpMessage()
}, 500)
}
}
//
this.messageListener = (type, message, parsedArray) => {
const messageObj = {
type: type,
content: message,
parsedArray: parsedArray,
timestamp: new Date().toLocaleTimeString(),
direction: 'received'
}
console.log("0000")
this.tcpMessages.push(messageObj)
// console.log('TCP:', messageObj)
console.log('home开始调用parseStockData',messageObj)
//
this.parseStockData(message)
}
//
tcpConnection.onConnectionChange(this.connectionListener)
tcpConnection.onMessage(this.messageListener)
},
// TCP
connectTcp() {
console.log('开始连接TCP服务器...')
//
if (this.tcpConnected) {
console.log('检测到现有连接,先断开...')
this.disconnectTcp()
//
setTimeout(() => {
this.performTcpConnect()
}, 500)
} else {
//
this.performTcpConnect()
}
},
// TCP
performTcpConnect() {
console.log('执行TCP连接...')
tcpConnection.connect(
{
ip: '192.168.1.9',
port: '8080',
channel: '1', // 1~20
charsetname: 'UTF-8' // UTF-8GBK
}
)
},
// TCP
disconnectTcp() {
console.log('断开TCP连接...')
tcpConnection.disconnect(
{
ip: '192.168.1.9',
port: '8080',
channel: '1', // 1~20
charsetname: 'UTF-8' // UTF-8GBK
}
)
this.tcpConnected = false
},
// TCP
sendTcpMessage() {
//
const messageData =
// {
// command: "real_time",
// stock_code: "SH.000001"
// }
// {"command": "stock_list"}
// {"command": "batch_real_time", "stock_codes": ["SH.000001"]}
{"command": "batch_real_time", "stock_codes": ["SH.000001","SH.000002","SH.000003"]}
//
const success = tcpConnection.send(messageData)
if (success) {
console.log('home发送TCP消息:', messageData)
uni.showToast({
title: '服务器连接成功',
icon: 'success',
duration: 1500
})
}
},
//
clearTcpMessages() {
this.tcpMessages = []
uni.showToast({
title: '消息记录已清空',
icon: 'success',
duration: 1500
})
},
// TCP
getTcpStatus() {
const status = tcpConnection.getConnectionStatus()
uni.showModal({
title: 'TCP连接状态',
content: `当前状态: ${status ? '已连接' : '未连接'}\n消息数量: ${this.tcpMessages.length}`,
showCancel: false
})
},
// countdata
validateDataIntegrity(parsedMessage) {
if (!parsedMessage.count || !parsedMessage.data) {
return false
}
const dataObjectCount = Object.keys(parsedMessage.data).length
const expectedCount = parsedMessage.count
console.log(`数据完整性验证: 期望${expectedCount}个对象,实际${dataObjectCount}个对象`)
return dataObjectCount === expectedCount
},
//
getDataStatus(message) {
if (typeof message !== 'string') {
return 'invalid'
}
const trimmedMessage = message.trim()
const startsWithBrace = trimmedMessage.startsWith('{')
const endsWithBrace = trimmedMessage.endsWith('}')
if (startsWithBrace && endsWithBrace) {
// {} -
console.log('检测到完整数据格式')
return 'complete'
} else if (startsWithBrace && !endsWithBrace) {
// {} -
console.log('检测到前半部分数据')
return 'first_part'
} else if (!startsWithBrace && endsWithBrace) {
// {} -
console.log('检测到后半部分数据')
return 'second_part'
} else {
// -
console.log('检测到无效数据格式')
return 'invalid'
}
},
//
cacheFirstPartData(message) {
this.tcpDataCache.firstPartData = message.trim()
this.tcpDataCache.isWaitingSecondPart = true
console.log('已缓存前半部分数据,长度:', this.tcpDataCache.firstPartData.length)
},
//
concatenateDataParts(secondPartMessage) {
const completeMessage = this.tcpDataCache.firstPartData + secondPartMessage.trim()
console.log('数据拼接完成,完整数据长度:', completeMessage.length)
//
this.clearStringFragmentCache()
return completeMessage
},
//
clearStringFragmentCache() {
this.tcpDataCache.firstPartData = ''
this.tcpDataCache.isWaitingSecondPart = false
console.log('字符串片段缓存已清空')
},
//
mergeDataToCache(parsedMessage) {
//
if (!this.tcpDataCache.isCollecting) {
this.tcpDataCache.isCollecting = true
this.tcpDataCache.expectedCount = parsedMessage.count
this.tcpDataCache.collectedData = {}
this.tcpDataCache.timestamp = parsedMessage.timestamp
this.tcpDataCache.type = parsedMessage.type
console.log('开始收集数据片段,期望总数:', parsedMessage.count)
}
//
if (parsedMessage.data) {
Object.assign(this.tcpDataCache.collectedData, parsedMessage.data)
console.log('数据片段已合并,当前已收集:', Object.keys(this.tcpDataCache.collectedData).length, '个对象')
}
},
//
isCacheDataComplete() {
const collectedCount = Object.keys(this.tcpDataCache.collectedData).length
const expectedCount = this.tcpDataCache.expectedCount
console.log(`缓存数据检查: 已收集${collectedCount}个,期望${expectedCount}`)
return collectedCount === expectedCount && collectedCount > 0
},
//
getCompleteDataFromCache() {
const completeData = {
count: this.tcpDataCache.expectedCount,
data: { ...this.tcpDataCache.collectedData },
stock_count: this.tcpDataCache.expectedCount,
timestamp: this.tcpDataCache.timestamp,
type: this.tcpDataCache.type
}
//
this.tcpDataCache.isCollecting = false
this.tcpDataCache.expectedCount = 0
this.tcpDataCache.collectedData = {}
this.tcpDataCache.timestamp = ''
this.tcpDataCache.type = ''
console.log('获取完整数据并清空缓存,数据对象数:', Object.keys(completeData.data).length)
return completeData
},
// TCP
parseStockData(message) {
try {
console.log('进入parseStockData, message类型:', typeof message, '长度:', message.length)
//
const dataStatus = this.getDataStatus(message)
let completeMessage = ''
switch (dataStatus) {
case 'complete':
//
console.log('检测到完整数据,直接处理')
completeMessage = message
break
case 'first_part':
//
console.log('检测到前半部分数据,开始缓存')
this.cacheFirstPartData(message)
return //
case 'second_part':
//
if (this.tcpDataCache.isWaitingSecondPart) {
console.log('检测到后半部分数据,开始拼接')
completeMessage = this.concatenateDataParts(message)
} else {
console.log('收到后半部分数据,但没有缓存的前半部分,跳过处理')
return
}
break
case 'invalid':
default:
console.log('数据格式无效,跳过处理')
return
}
// JSON
let parsedMessage
try {
console.log('开始解析完整JSON数据,长度:', completeMessage.length)
parsedMessage = JSON.parse(completeMessage)
console.log('JSON解析成功,解析后类型:', typeof parsedMessage, parsedMessage)
} catch (parseError) {
console.error('JSON解析失败:', parseError.message)
//
this.clearStringFragmentCache()
return
}
//
if (!((parsedMessage.type === 'batch_data_chunk' || parsedMessage.type === 'batch_realtime_data') && parsedMessage.data)) {
console.log('不是batch_data_chunk或batch_realtime_data类型的消息,跳过处理')
return
}
console.log('开始处理股票数据')
//
// batch_data_chunk
if (parsedMessage.type === 'batch_data_chunk') {
console.log('batch_data_chunk类型数据,跳过完整性验证,直接处理')
this.processCompleteStockData(parsedMessage)
} else {
const isDataComplete = this.validateDataIntegrity(parsedMessage)
if (isDataComplete) {
//
console.log('对象级数据完整,直接处理')
this.processCompleteStockData(parsedMessage)
} else {
//
console.log('对象级数据不完整,开始拼接处理')
//
this.mergeDataToCache(parsedMessage)
//
if (this.isCacheDataComplete()) {
console.log('缓存数据已完整,开始处理')
const completeData = this.getCompleteDataFromCache()
this.processCompleteStockData(completeData)
} else {
console.log('缓存数据仍不完整,等待更多数据片段')
}
}
}
} catch (error) {
console.error('解析TCP股票数据失败:', error.message)
console.error('错误详情:', error)
//
this.clearStringFragmentCache()
if (this.tcpDataCache.isCollecting) {
console.log('发生错误,清空对象级数据缓存')
this.tcpDataCache.isCollecting = false
this.tcpDataCache.expectedCount = 0
this.tcpDataCache.collectedData = {}
this.tcpDataCache.timestamp = ''
this.tcpDataCache.type = ''
}
}
},
//
processCompleteStockData(completeData) {
console.log("开始更新TCP股票数据存储")
// TCP
this.tcpStockData = {
count: completeData.count || 0,
data: completeData.data || {},
stock_count: completeData.stock_count || 0,
timestamp: completeData.timestamp || '',
type: completeData.type || ''
}
// 3
const stockCodes = Object.keys(completeData.data)
const newStockInfoList = []
// 3
for (let i = 0; i < Math.min(stockCodes.length, 3); i++) {
const stockCode = stockCodes[i]
//
if (completeData.data[stockCode] && Array.isArray(completeData.data[stockCode]) && completeData.data[stockCode].length > 0) {
const stockData = completeData.data[stockCode][0] //
if (stockData && stockData.current_price !== undefined && stockData.pre_close !== undefined) {
//
const changeValue = stockData.current_price - stockData.pre_close
const changePercent = ((changeValue / stockData.pre_close) * 100).toFixed(2)
const changeSign = changeValue >= 0 ? '+' : ''
//
newStockInfoList.push({
stock_name: stockData.stock_name || '未知股票',
current_price: stockData.current_price ? stockData.current_price.toFixed(2) : '0.00',
change: `${changeSign}${changePercent}%`,
change_value: changeValue,
change_percent: parseFloat(changePercent)
})
}
}
}
//
if (newStockInfoList.length > 0) {
this.currentStockInfoList = newStockInfoList
console.log('股票数据更新成功,共', newStockInfoList.length, '个股票:', this.currentStockInfoList)
}
},
// TCP
removeTcpListeners() {
if (this.connectionListener) {
tcpConnection.removeConnectionListener(this.connectionListener)
this.connectionListener = null
console.log('已移除TCP连接状态监听器')
}
if (this.messageListener) {
tcpConnection.removeMessageListener(this.messageListener)
this.messageListener = null
console.log('已移除TCP消息监听器')
}
} }
} }
} }
@ -726,4 +1274,155 @@ export default {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
/* TCP测试区域样式 */
.tcp-test-section {
border: 1px solid #e0e0e0;
background-color: #f9f9f9;
}
.tcp-status {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
}
.tcp-status.connected {
background-color: #e8f5e8;
color: #4caf50;
border: 1px solid #4caf50;
}
.tcp-status.disconnected {
background-color: #ffeaea;
color: #f44336;
border: 1px solid #f44336;
}
.status-text {
font-size: 12px;
font-weight: bold;
}
.tcp-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 15px;
}
.tcp-btn {
flex: 1;
min-width: 80px;
height: 36px;
border-radius: 6px;
font-size: 12px;
border: none;
color: white;
font-weight: bold;
}
.connect-btn {
background-color: #4caf50;
}
.connect-btn:disabled {
background-color: #cccccc;
}
.disconnect-btn {
background-color: #f44336;
}
.disconnect-btn:disabled {
background-color: #cccccc;
}
.send-btn {
background-color: #2196f3;
}
.send-btn:disabled {
background-color: #cccccc;
}
.status-btn {
background-color: #ff9800;
}
.tcp-messages {
margin-top: 15px;
border-top: 1px solid #e0e0e0;
padding-top: 15px;
}
.messages-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.messages-title {
font-size: 14px;
font-weight: bold;
color: #333;
}
.clear-btn {
padding: 4px 8px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
font-size: 10px;
}
.messages-list {
max-height: 200px;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 8px;
background-color: white;
}
.message-item {
margin-bottom: 8px;
padding: 8px;
border-radius: 6px;
border-left: 3px solid #ccc;
}
.message-item.sent {
background-color: #e3f2fd;
border-left-color: #2196f3;
}
.message-item.received {
background-color: #f1f8e9;
border-left-color: #4caf50;
}
.message-info {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.message-direction {
font-size: 10px;
font-weight: bold;
color: #666;
}
.message-time {
font-size: 10px;
color: #999;
}
.message-content {
font-size: 12px;
color: #333;
word-break: break-all;
}
</style> </style>
Loading…
Cancel
Save