处理做市商列表
This commit is contained in:
parent
38d2cf7532
commit
00972fea53
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
||||
// 返回客户端实例,便于链式调用或外部访问
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
//风险金
|
||||
|
|
|
|||
|
|
@ -186,8 +186,10 @@ const handleLogin = async () => {
|
|||
// 开启 loading
|
||||
startButtonLoading()
|
||||
try {
|
||||
// 生成device_no并保存到变量(关键修改1)
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue