From 21938f330fc083f72ebbd2875f8f4ac3aeea0a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8?= Date: Tue, 7 Jul 2026 16:23:13 +0800 Subject: [PATCH] . --- src/api/modules/trade/profitStatistics.ts | 60 ++++++++ src/components/layout/Tabs.vue | 21 ++- src/stores/modules/tabs.ts | 7 +- src/views/trade/profitStatistics/index.vue | 145 ++++++++++++++++++++ src/views/trade/profitStatistics/options.ts | 88 ++++++++++++ src/views/trade/profitStatistics/rules.ts | 7 + 6 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 src/api/modules/trade/profitStatistics.ts create mode 100644 src/views/trade/profitStatistics/index.vue create mode 100644 src/views/trade/profitStatistics/options.ts create mode 100644 src/views/trade/profitStatistics/rules.ts diff --git a/src/api/modules/trade/profitStatistics.ts b/src/api/modules/trade/profitStatistics.ts new file mode 100644 index 0000000..4149dcb --- /dev/null +++ b/src/api/modules/trade/profitStatistics.ts @@ -0,0 +1,60 @@ +/** + * 账户盈利统计 - 接口 + * + * 后端约定: + * - GET /api/trade/profit/statistics 查询账户盈利统计列表 + * - POST /api/trade/profit/export 导出账户盈利统计(后端返回流或 url) + * + * 入参: + * - mainAccountName 主账户名 + * - subAccountName 子账户名 + * - startDate 开始日期 (YYYY-MM-DD) + * - endDate 结束日期 (YYYY-MM-DD) + * + * 出参(列表项): + * - id 唯一标识 + * - mainAccountName 主账户名 + * - subAccountName 子账户名 + * - profitAmount 盈利金额 + * - orderCount 单数 + * - lotCount 手数 + * - winRate 赢率(0~1 或 0~100) + * + * 说明:项目内 request 是带 get/post 方法的对象(见 src/api/index.ts), + * 所以这里使用 request.get / request.post 调用方式。 + */ +import { request } from '@/api' + +export interface IProfitStatisticsQuery { + mainAccountName?: string + subAccountName?: string + startDate?: string + endDate?: string +} + +export interface IProfitStatisticsItem { + id?: string | number + mainAccountName?: string + subAccountName?: string + profitAmount?: number | string + orderCount?: number | string + lotCount?: number | string + winRate?: number | string + [key: string]: any +} + +/** 查询账户盈利统计列表 */ +export const getProfitStatisticsList = (params: IProfitStatisticsQuery) => { + return request.get<{ + list?: IProfitStatisticsItem[] + records?: IProfitStatisticsItem[] + data?: IProfitStatisticsItem[] + }>('/api/trade/profit/statistics', params) +} + +/** 导出账户盈利统计(后端返回文件流 / 下载链接) */ +export const exportProfitStatistics = (params: IProfitStatisticsQuery) => { + return request.post('/api/trade/profit/export', params, { + responseType: 'blob', + } as any) +} diff --git a/src/components/layout/Tabs.vue b/src/components/layout/Tabs.vue index 26f4714..cc632f3 100644 --- a/src/components/layout/Tabs.vue +++ b/src/components/layout/Tabs.vue @@ -258,19 +258,34 @@ onMounted(() => { router.replace({ path: targetTab.path, query: targetTab.query }) } } - + // 初始检查滚动状态 nextTick(checkScroll) - + // 添加滚动监听 tabsContainerRef.value?.addEventListener('scroll', checkScroll) - + // 全局左键点击关闭右键菜单 document.addEventListener('click', handleGlobalClick) // 全局右键监听,如果右键在其他tab上,关闭当前右键菜单 document.addEventListener('contextmenu', handleGlobalContextMenu) }) +// 监听动态路由恢复完成(router/index.ts 中通过 dynamicRoutesReady 标记), +// 在动态路由加载完毕后,如果当前路由与保存的激活 tab 不一致,则跳转过去 +watch( + () => route.fullPath, + () => { + // 仅当保存的激活 tab 存在且与当前路由不一致时才跳转 + if (tabsStore.activeTab && tabsStore.activeTab !== route.path) { + const targetTab = tabsStore.tabs.find(item => item.path === tabsStore.activeTab) + if (targetTab && targetTab.path !== route.path) { + router.replace({ path: targetTab.path, query: targetTab.query }) + } + } + } +) + onUnmounted(() => { tabsContainerRef.value?.removeEventListener('scroll', checkScroll) document.removeEventListener('click', handleGlobalClick) diff --git a/src/stores/modules/tabs.ts b/src/stores/modules/tabs.ts index 5d997f3..c9c1c1b 100644 --- a/src/stores/modules/tabs.ts +++ b/src/stores/modules/tabs.ts @@ -128,18 +128,21 @@ export const useTabsStore = defineStore('tabs', () => { const existIndex = tabs.value.findIndex(item => item.path === tab.path) // 计算面包屑路径 const breadcrumbs = findBreadcrumbPath(tab.path) - + if (existIndex === -1) { // 新标签页,添加到列表 tabs.value.push({ ...tab, breadcrumbs }) + // 只有新增标签时才激活它 + activeTab.value = tab.path } else { // 已存在的标签页,更新面包屑(确保数据是最新的) const existingTab = tabs.value[existIndex] if (existingTab) { existingTab.breadcrumbs = breadcrumbs } + // 已存在的标签,不要覆盖当前激活状态, + // 避免在页面刷新/路由重新匹配时把激活的 tab 错误地切回当前路由 } - activeTab.value = tab.path saveTabs() } diff --git a/src/views/trade/profitStatistics/index.vue b/src/views/trade/profitStatistics/index.vue new file mode 100644 index 0000000..aa7b35f --- /dev/null +++ b/src/views/trade/profitStatistics/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/views/trade/profitStatistics/options.ts b/src/views/trade/profitStatistics/options.ts new file mode 100644 index 0000000..d6f6fb9 --- /dev/null +++ b/src/views/trade/profitStatistics/options.ts @@ -0,0 +1,88 @@ +/** + * 账户盈利统计 - 搜索/表格列配置 + */ +import type { Ref } from 'vue' + +/** + * 搜索表单字段配置(传入 Search 通用组件自动渲染) + * - 主账户名 / 子账户名:input + * - 时间范围:daterange + */ +export const searchOptions: Ref | any[] = [ + // 主账户名 + { + prop: 'mainAccountName', + label: '主账户名', + type: 'input' as const, + placeholder: '请输入主账户名', + clearable: true, + width: '200px', + }, + // 子账户名 + { + prop: 'subAccountName', + label: '子账户名', + type: 'input' as const, + placeholder: '请输入子账户名', + clearable: true, + width: '200px', + }, + // 时间范围 + { + prop: 'dateRange', + label: '时间日期', + type: 'date' as const, + dateType: 'daterange' as const, + rangeSeparator: '至', + startPlaceholder: '开始日期', + endPlaceholder: '结束日期', + valueFormat: 'YYYY-MM-DD', + format: 'YYYY-MM-DD', + width: '320px', + clearable: true, + }, +] + +/** + * 表格列配置(传入 Table 通用组件自动渲染) + */ +export const tableColumns = [ + // 序号列 + { type: 'index', label: '序号', align: 'center' as const, width: 60 }, + // 主账户名 + { prop: 'mainAccountName', label: '主账户名', align: 'center' as const, minWidth: 140 }, + // 子账户名 + { prop: 'subAccountName', label: '子账户名', align: 'center' as const, minWidth: 140 }, + // 盈利金额 + { + prop: 'profitAmount', + label: '盈利金额', + align: 'right' as const, + minWidth: 140, + isNumber: true, + }, + // 单数 + { + prop: 'orderCount', + label: '单数', + align: 'center' as const, + minWidth: 100, + isNumber: true, + }, + // 手数 + { + prop: 'lotCount', + label: '手数', + align: 'center' as const, + minWidth: 100, + isNumber: true, + }, + // 赢率(百分比展示) + { + prop: 'winRate', + label: '赢率', + align: 'center' as const, + minWidth: 120, + slotName: 'winRate', + }, +] diff --git a/src/views/trade/profitStatistics/rules.ts b/src/views/trade/profitStatistics/rules.ts new file mode 100644 index 0000000..ae92b5e --- /dev/null +++ b/src/views/trade/profitStatistics/rules.ts @@ -0,0 +1,7 @@ +/** + * 账户盈利统计 - 表单校验 + * + * 当前搜索字段均为非必填(用户可不指定主/子账户、不指定时间范围进行全量查询), + * 因此保留空对象占位,便于后续扩展。 + */ +export const rules = {}