处理做市商列表

This commit is contained in:
2026-05-27 14:31:16 +08:00
parent 38d2cf7532
commit 00972fea53
8 changed files with 171 additions and 27 deletions

View File

@ -102,6 +102,15 @@ const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>
data.msg = '未知的错误!'
}
break
case -100:
// 退出登录
data.msg = 'token过期'
removeStorage('token')
removeStorage('userInfo')
removeStorage('menusInfo')
removeStorage('permissionsInfo')
router.push({ name: 'Login' })
break
case -101:
data.msg = 'token错误'
removeStorage('token')

View File

@ -99,3 +99,12 @@ export async function checkAccountIdIsSonForUser(params: { account_id: string })
export async function getUserBaseInfo(params: { account_id: string }) {
return request.get<boolean>('/user/getUserBaseInfo', params)
}
/**
* MQTT Key
* @param params
* @returns Promise<string> MQTT key
*/
export async function initMqttKeyApi(params: { device_no: string }) {
return request.post<string>('/initMqttKey', params)
}

View File

@ -3,6 +3,7 @@ import { ref } from 'vue'
import { MQTTClient } from '@/utils/mqttClient'
import { useUserStore } from './user'
import { useOrderPositionStore } from '@/stores/modules/orderPosition.ts'
import { formatDate } from '@/utils/timeUtils'
// ============================================================
// 类型声明
@ -24,16 +25,13 @@ export interface PositionItem {
[key: string]: any
}
/** 用户信息最小化类型(用于类型断言) */
interface UserInfo {
redis_key: string
token: string
/** 做市商浮动盈亏数据 */
export interface MarketFloatProfitData {
account_id: string
float_profit_loss: string
update_time: string
}
/** Vite 环境变量扩展(如项目已有 env.d.ts 可忽略) */
declare const __VITE_MQTT_TRANSACTION_HOST__: string
// ============================================================
// Store 定义
// ============================================================
@ -47,6 +45,8 @@ export const useTransactionStore = defineStore('transaction', () => {
// ------------------ 状态 ------------------
/** 持仓列表 */
const positionList = ref<PositionItem[]>([])
/** 做市商浮动盈亏数据 Map<account_id, float_profit_loss> */
const marketFloatProfitMap = ref<Map<string, string>>(new Map())
// ------------------ 初始化 ------------------
@ -55,12 +55,21 @@ export const useTransactionStore = defineStore('transaction', () => {
*
*/
const initMarketConditionsMQTT = async (): Promise<void> => {
const { redis_key, token } = userStore.userInfo as UserInfo
console.log("initMarketConditionsMQTT", redis_key, token)
const { redis_key, token } = userStore.userInfo as any
console.log("initMarketConditionsMQTT", redis_key, token)
try {
// 先调用 initMqttKey 接口获取 MQTT key
// 使用 mqttKey 连接
await mqttClient.connect(import.meta.env.VITE_MQTT_TRANSACTION_HOST as string, {
username: `${redis_key}`,
password: `${token}`,
})
} catch (error) {
console.error('初始化 MQTT 连接失败:', error)
return
}
await subscribeTheme()
}
@ -70,9 +79,9 @@ console.log("initMarketConditionsMQTT", redis_key, token)
* POSITION_FLOATING_BACK
*/
const subscribeTheme = async (): Promise<void> => {
const { id } = userStore.userInfo as UserInfo
const { id } = userStore.userInfo as any
if (mqttClient.isConnected) {
if (mqttClient.getConnected()) {
const topic = `push/${id}`
await mqttClient.subscribe([topic])
@ -80,13 +89,19 @@ console.log("initMarketConditionsMQTT", redis_key, token)
[topic]: (_topic: string, msg: string): void => {
console.log(_topic, msg)
const [type, data] = msg.split('|')
let [type, data] = msg.split('|')
switch (type) {
case 'POSITION_FLOATING_BACK': {
// 持仓列表回调:交由 orderStore 处理
orderStore.handlePositionOrderList(data)
break
}
case 'SUBSCRIBE_AMOUNT_FLOATING_BACK': {
// 做市商浮动盈亏回调:更新 marketFloatProfitMap
console.log('做市商浮动盈亏数据:', data);
handleMarketFloatProfit(data)
break
}
default:
break
}
@ -97,6 +112,28 @@ console.log("initMarketConditionsMQTT", redis_key, token)
}
}
/**
*
* account_id:float_profit_loss (84:6992.730000)
*/
const handleMarketFloatProfit = (data: string): void => {
if (!data) return
const fields = data.split(':')
if (fields.length < 2) return
const account_id = fields[0]
const float_profit_loss = fields[1]
// 更新 Map
marketFloatProfitMap.value.set(account_id, float_profit_loss)
// 触发响应式更新(创建新的 Map 实例)
marketFloatProfitMap.value = new Map(marketFloatProfitMap.value)
console.log(`做市商浮动盈亏更新: account=${account_id}, float=${float_profit_loss}`)
}
// ------------------ 持仓列表 ------------------
/**
@ -105,7 +142,8 @@ console.log("initMarketConditionsMQTT", redis_key, token)
*/
const getPositionList = (account_id: string): void => {
if (mqttClient.isConnected) {
console.log('mqttClient.getConnected():', mqttClient.getConnected());
if (mqttClient.getConnected()) {
console.log('持仓 POSITION 持仓列表数据', `POSITION::${account_id}`)
mqttClient.publish('server/trade', `POSITION::${account_id}`)
} else {
@ -113,20 +151,45 @@ console.log("initMarketConditionsMQTT", redis_key, token)
}
}
/**
*
* SUBSCRIBE_AMOUNT_FLOATING
* @param account_id ID
*/
const requestMarketFloatProfit = (): void => {
console.log('mqttClient.getConnected():', mqttClient.getConnected());
if (mqttClient.getConnected()) {
mqttClient.publish('server/trade', `SUBSCRIBE_AMOUNT_FLOATING`)
} else {
setTimeout(() => requestMarketFloatProfit(), 300)
}
}
/**
*
* */
const disconnect = () => {
mqttClient?.disconnect()
positionList.value = []
marketFloatProfitMap.value.clear()
}
/**
* MQTT
*/
const isConnected = (): boolean => {
return mqttClient.getConnected()
}
// ------------------ 暴露 ------------------
return {
mqttClient,
positionList,
marketFloatProfitMap,
initMarketConditionsMQTT,
getPositionList,
disconnect
requestMarketFloatProfit,
disconnect,
isConnected
}
})

View File

@ -17,6 +17,7 @@ import type {
import type { UserInfo, MenuItem, PermissionItem } from '@/stores/types/user'
import { formatMenuToElMenu, type ElMenuItem } from '@/utils/menuFormat'
import { handleRouter, setRouter } from '@/router/handleRouter.ts'
import { useTransactionStore } from './transaction'
// 定义用户 Store
export const useUserStore = defineStore('user', () => {
@ -74,8 +75,12 @@ export const useUserStore = defineStore('user', () => {
removeStorage('userInfo')
removeStorage('menusInfo')
removeStorage('permissionsInfo')
removeStorage('device_no')
// removeStorage('device_no')
console.log('用户信息已清空')
// 断开 MQTT 连接
const transactionStore = useTransactionStore()
transactionStore.disconnect()
}
/**

View File

@ -1,6 +1,7 @@
import mqtt from 'mqtt'
import type { MqttClient, IClientOptions, IClientPublishOptions } from 'mqtt'
import type { QoS } from 'mqtt-packet'
import { initMqttKeyApi } from '@/api/modules/user'
/**
*
*
@ -106,9 +107,13 @@ class MQTTClient {
*
*
*/
this.client.on('close', () => {
this.client.on('close', async () => {
console.log('🔌 MQTT 连接关闭')
this.isConnected = false
const foreignDeviceNo = sessionStorage.getItem('foreign_device_no') || ''
const mqttKey = await initMqttKeyApi({ device_no: foreignDeviceNo })
console.log("initMqttKey result:", mqttKey)
})
// 返回客户端实例,便于链式调用或外部访问

View File

@ -32,6 +32,9 @@
@row-click="handleRowClick"
@row-contextmenu="handleRowContextMenu"
>
<template #floatProfitLoss="{ row }">
<span>{{ getMarketFloatProfit(row.id) }}</span>
</template>
<template #pointSpread="{ row }">
<el-button type="primary" size="small" @click="handleViewPointDiff(row)"> 查看 </el-button>
</template>
@ -248,6 +251,29 @@ const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, st
const marketTree = ref([])
const marketTableColumnsOptions = ref(marketTableColumns)
// transactionStore
import { useTransactionStore } from '@/stores/modules/transaction.ts'
const transactionStore = useTransactionStore()
/**
* @description 获取做市商的浮动盈亏值
*/
const getMarketFloatProfit = (accountId: string | number): string => {
const profit = transactionStore.marketFloatProfitMap.get(String(accountId))
return profit || '--'
}
/**
* @description 获取浮动盈亏样式类
*/
const getFloatProfitClass = (value: string | number) => {
const num = Number(value)
if (isNaN(num)) return ''
if (num > 0) return 'profit-positive'
if (num < 0) return 'profit-negative'
return ''
}
/**
* @description 定义分页数据
*/
@ -485,7 +511,7 @@ const mapMarketTree = (data: any) => {
pointSpreadCommission: item.wallet_capital.commission_point_diff, //
commissionRatio: (item.wallet_capital.fee_handling_percent * 1).toFixed(2), //
commissionReturn: item.wallet_capital.commission_fee_handling, //
floatProfitLoss: '后续添加', //
floatProfitLoss: '后续添加', //使transactionStore
closeProfitLoss: item.wallet_capital.closing_profit, //
riskMargin: item.wallet_capital.risk, //
serviceFee: item.wallet_capital.service, //
@ -493,6 +519,8 @@ const mapMarketTree = (data: any) => {
//使
mobile: item.mobile || '--',
email: item.email || '--',
// ID
accountId: item.wallet_capital.account_id,
}))
}
@ -578,7 +606,7 @@ const getMarketList = async () => {
//
marketTree.value = mapMarketTree(data.data)
total.value = data.total
requestAllMarketFloatProfit()
//
stopLoading()
currentPage.value = data.current_page
@ -595,14 +623,29 @@ const getRiskTemplateList = async () => {
}))
}
/**
* @description 请求所有做市商的浮动盈亏数据
*/
const requestAllMarketFloatProfit = () => {
transactionStore.requestMarketFloatProfit()
}
/**
* @description 页面加载完成后需要获取的数据
*/
onMounted(() => {
onMounted(async () => {
getMarketList()
getRiskTemplateList()
// MQTT
if (userStore.isLoggedIn && !transactionStore.isConnected()) {
transactionStore.initMarketConditionsMQTT()
}
})
// logout
/**
* @description 监听数据变化
*/
@ -959,4 +1002,12 @@ const handleCancel = async () => {
padding-top: 20px;
border-top: 1px solid #ebeef5;
}
//
.profit-positive {
color: #67C23A;
}
.profit-negative {
color: #F56C6C;
}
</style>

View File

@ -47,7 +47,7 @@ export const marketTableColumns = [
//手续费返佣
{ label: '手续费返佣', prop: 'commissionReturn', align: 'center' as const, width: '150px', isNumber: true },
//浮动盈亏
{ label: '浮动盈亏', prop: 'floatProfitLoss', align: 'center' as const, width: '100px', isNumber: true },
{ label: '浮动盈亏', slotName: 'floatProfitLoss', align: 'center' as const, width: '100px', isNumber: true },
//平仓盈亏
{ label: '平仓盈亏', prop: 'closeProfitLoss', align: 'center' as const, width: '150px', isNumber: true },
//风险金

View File

@ -186,8 +186,10 @@ const handleLogin = async () => {
// loading
startButtonLoading()
try {
// device_no1
const deviceNo = generateTimestampWithRandomNum()
// storage device_no
const existingDeviceNo = getStorage('device_no', 'session')
console.log('existingDeviceNo:', existingDeviceNo);
const deviceNo = existingDeviceNo || generateTimestampWithRandomNum()
//
const loginParams = {
email: loginForm.value.email,
@ -195,7 +197,7 @@ const handleLogin = async () => {
login_type: getStorage('loginType', 'session') === 'email' ? 1 : 2,
code: loginForm.value.code,
password: loginForm.value.password,
device_no: deviceNo,
device_no: deviceNo+'',
}
await userStore.login(loginParams)
setStorage('device_no', deviceNo)