This commit is contained in:
2026-07-07 16:23:13 +08:00
parent 019d09df3e
commit 21938f330f
6 changed files with 323 additions and 5 deletions

View File

@ -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<Blob>('/api/trade/profit/export', params, {
responseType: 'blob',
} as any)
}

View File

@ -271,6 +271,21 @@ onMounted(() => {
document.addEventListener('contextmenu', handleGlobalContextMenu) 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(() => { onUnmounted(() => {
tabsContainerRef.value?.removeEventListener('scroll', checkScroll) tabsContainerRef.value?.removeEventListener('scroll', checkScroll)
document.removeEventListener('click', handleGlobalClick) document.removeEventListener('click', handleGlobalClick)

View File

@ -132,14 +132,17 @@ export const useTabsStore = defineStore('tabs', () => {
if (existIndex === -1) { if (existIndex === -1) {
// 新标签页,添加到列表 // 新标签页,添加到列表
tabs.value.push({ ...tab, breadcrumbs }) tabs.value.push({ ...tab, breadcrumbs })
// 只有新增标签时才激活它
activeTab.value = tab.path
} else { } else {
// 已存在的标签页,更新面包屑(确保数据是最新的) // 已存在的标签页,更新面包屑(确保数据是最新的)
const existingTab = tabs.value[existIndex] const existingTab = tabs.value[existIndex]
if (existingTab) { if (existingTab) {
existingTab.breadcrumbs = breadcrumbs existingTab.breadcrumbs = breadcrumbs
} }
// 已存在的标签,不要覆盖当前激活状态,
// 避免在页面刷新/路由重新匹配时把激活的 tab 错误地切回当前路由
} }
activeTab.value = tab.path
saveTabs() saveTabs()
} }

View File

@ -0,0 +1,145 @@
<template>
<div class="profit-statistics table_form-container">
<!-- 搜索 -->
<ProfitStatisticsSearch
:search-form="searchForm"
:button-loading="loading"
:search-options="searchOptions"
@search="handleSearch"
@reset="handleReset"
>
<!-- 操作按钮导出 -->
<template #button>
<el-button
type="primary"
:loading="exportLoading"
:disabled="!tableData.length"
v-auth="'trade_profitStatistics_export'"
@click="handleExport"
>
导出
</el-button>
</template>
</ProfitStatisticsSearch>
<!-- 表格 -->
<ProfitStatisticsTable
:table-data="tableData"
:columns="tableColumnsOptions"
:loading="loading"
row-key="id"
border
highlight-current-row
>
<!-- 赢率后端返回 0~1 的小数这里转换为百分比 -->
<template #winRate="{ row }">
<span>{{ formatWinRate(row.winRate) }}</span>
</template>
</ProfitStatisticsTable>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import ProfitStatisticsSearch from '@/components/common/Search.vue'
import ProfitStatisticsTable from '@/components/common/Table.vue'
import { searchOptions, tableColumns } from './options'
import { rules as _rules } from './rules'
import { getProfitStatisticsList } from '@/api/modules/trade/profitStatistics'
import { exportToExcel } from '@/utils/exportToExcel'
//
const searchForm = ref({
mainAccountName: '',
subAccountName: '',
dateRange: [], // [startDate, endDate]
})
//
const tableData = ref<any[]>([])
const tableColumnsOptions = ref<any[]>(tableColumns)
//
const loading = ref(false)
const exportLoading = ref(false)
/**
* 搜索
*/
const handleSearch = async () => {
try {
loading.value = true
const [startDate = '', endDate = ''] = (searchForm.value.dateRange as string[]) || []
const { data } = await getProfitStatisticsList({
mainAccountName: searchForm.value.mainAccountName || undefined,
subAccountName: searchForm.value.subAccountName || undefined,
startDate: startDate || undefined,
endDate: endDate || undefined,
})
tableData.value = Array.isArray(data) ? data : (data as any)?.list || (data as any)?.records || []
} catch (e: any) {
ElMessage.error(e?.message || '查询失败')
tableData.value = []
} finally {
loading.value = false
}
}
/**
* 重置
*/
const handleReset = () => {
searchForm.value.mainAccountName = ''
searchForm.value.subAccountName = ''
searchForm.value.dateRange = []
}
/**
* 导出
* 复用项目通用导出工具导出当前查询结果
*/
const handleExport = async () => {
if (!tableData.value.length) {
ElMessage.warning('当前没有可导出的数据')
return
}
try {
exportLoading.value = true
await exportToExcel({
tableData: tableData.value,
columns: tableColumnsOptions.value,
title: '账户盈利统计',
filename: `账户盈利统计_${new Date().getTime()}`,
})
} catch (e: any) {
ElMessage.error(e?.message || '导出失败')
} finally {
exportLoading.value = false
}
}
/**
* 赢率格式化为百分比0.62 -> 62.00%
*/
const formatWinRate = (val: any) => {
if (val === null || val === undefined || val === '') return '-'
const num = Number(val)
if (Number.isNaN(num)) return '-'
// 0~100 0~1
const rate = num > 1 ? num : num * 100
return `${rate.toFixed(2)}%`
}
onMounted(() => {
// ""
})
</script>
<style scoped lang="scss">
.profit-statistics {
.cell-content {
display: inline-block;
}
}
</style>

View File

@ -0,0 +1,88 @@
/**
* - /
*/
import type { Ref } from 'vue'
/**
* Search
* - / input
* - daterange
*/
export const searchOptions: Ref<any[]> | 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',
},
]

View File

@ -0,0 +1,7 @@
/**
* -
*
* /
* 便
*/
export const rules = {}