This commit is contained in:
parent
019d09df3e
commit
21938f330f
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
},
|
||||
]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* 账户盈利统计 - 表单校验
|
||||
*
|
||||
* 当前搜索字段均为非必填(用户可不指定主/子账户、不指定时间范围进行全量查询),
|
||||
* 因此保留空对象占位,便于后续扩展。
|
||||
*/
|
||||
export const rules = {}
|
||||
Loading…
Reference in New Issue