树形数据逻辑调整

This commit is contained in:
zhonghua.li
2026-04-30 09:38:22 +08:00
parent ed97d027f6
commit f10de286cc

View File

@@ -1,5 +1,7 @@
<template>
<div class="app-container">
<el-tabs v-model="activeTab" class="daily-report-tabs" @tab-click="handleMainTabClick">
<el-tab-pane label="报表列表" name="list">
<el-card class="filter-container" shadow="never">
<el-form :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="昵称">
@@ -46,10 +48,9 @@
<el-button type="warning" icon="el-icon-data-analysis" :loading="reportSumLoading" @click="openReportSumDialog">汇总数据</el-button>
<el-button type="danger" icon="el-icon-delete" :loading="deleteByDateLoading" @click="openDeleteByDateDialog">删除报表数据</el-button>
<el-button type="success" icon="el-icon-download" :loading="exportLoading" @click="openExportDialog">报表导出</el-button>
<el-button type="info" icon="el-icon-s-grid" :loading="deptStatsLoading" @click="openDeptStatsDialog">统计部门金额</el-button>
</div>
<div class="action-tip">
说明服务费=当日买入×0.01差额=当日卖出当日买入应收应付=差额服务费>0 应收当前人<0 应付当前人
说明服务费=当日买入×0.01差额=当日卖出当日买入应收应付=差额服务费&gt;0 应收当前人&lt;0 应付当前人
</div>
</div>
</el-card>
@@ -99,6 +100,98 @@
@current-change="handleCurrentChange"
/>
</el-card>
</el-tab-pane>
<el-tab-pane label="统计部门金额" name="deptStats">
<el-card shadow="never" class="dept-stats-panel">
<el-form :inline="true" label-width="88px" class="dept-stats-filter">
<el-form-item label="开始日期">
<el-date-picker
v-model="deptStatsForm.startDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="开始日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="deptStatsForm.endDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="结束日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" :loading="deptStatsLoading" @click="fetchDeptStatsTree">查询</el-button>
</el-form-item>
</el-form>
<p v-if="deptStatsSummaryText" class="dept-stats-summary">{{ deptStatsSummaryText }}</p>
<div class="dept-stats-table-wrap">
<el-table
v-loading="deptStatsLoading"
:data="deptStatsTreeData"
row-key="userId"
border
:stripe="false"
:indent="40"
:height="deptStatsTableHeight"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:row-class-name="deptStatsRowClassName"
class="dept-stats-tree-table"
style="width: 100%"
>
<el-table-column label="姓名 / 用户ID" prop="name" min-width="240" fixed show-overflow-tooltip>
<template slot-scope="scope">
<span class="dept-stats-name-wrap">
<i :class="['dept-stats-level-icon', deptStatsLevelIconClass(scope.row)]" />
<span class="dept-stats-name-text">{{ scope.row.name || '-' }}</span>
<span class="dept-stats-user-id">{{ scope.row.userId }}</span>
</span>
</template>
</el-table-column>
<el-table-column label="本人当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="团队当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="合计当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="本人昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="团队昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="合计昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="本人当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="团队当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="合计当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="本人差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDiffAmt) }}</template>
</el-table-column>
<el-table-column label="团队差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDiffAmt) }}</template>
</el-table-column>
<el-table-column label="合计差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDiffAmt) }}</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-tab-pane>
</el-tabs>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="760px" @close="resetForm">
<el-form ref="dataForm" :model="form" :rules="formRules" label-width="120px">
@@ -216,99 +309,6 @@
</div>
</el-dialog>
<el-dialog
title="统计部门金额"
:visible.sync="deptStatsDialogVisible"
width="96%"
top="4vh"
custom-class="dept-stats-dialog"
@close="resetDeptStatsDialog"
>
<el-form :inline="true" label-width="88px" class="dept-stats-filter">
<el-form-item label="开始日期">
<el-date-picker
v-model="deptStatsForm.startDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="开始日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="deptStatsForm.endDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="结束日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" :loading="deptStatsLoading" @click="fetchDeptStatsTree">查询</el-button>
</el-form-item>
</el-form>
<p v-if="deptStatsSummaryText" class="dept-stats-summary">{{ deptStatsSummaryText }}</p>
<div class="dept-stats-table-wrap">
<el-table
v-loading="deptStatsLoading"
:data="deptStatsTreeData"
row-key="userId"
border
stripe
height="520"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
style="width: 100%"
>
<el-table-column label="姓名 / 用户ID" prop="name" min-width="220" fixed show-overflow-tooltip>
<template slot-scope="scope">
<span>{{ scope.row.name || '-' }}</span>
<span class="dept-stats-user-id">{{ scope.row.userId }}</span>
</template>
</el-table-column>
<el-table-column label="本人昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="团队昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="合计昨日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalYestodayBuyAmt) }}</template>
</el-table-column>
<el-table-column label="本人当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="团队当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="合计当日卖出" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDailySellAmt) }}</template>
</el-table-column>
<el-table-column label="本人当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="团队当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="合计当日买入" min-width="118" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDailyBuyAmt) }}</template>
</el-table-column>
<el-table-column label="本人差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.ownDiffAmt) }}</template>
</el-table-column>
<el-table-column label="团队差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.teamDiffAmt) }}</template>
</el-table-column>
<el-table-column label="合计差额" min-width="100" align="right">
<template slot-scope="s">{{ formatDeptAmt(s.row.totalDiffAmt) }}</template>
</el-table-column>
</el-table>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="deptStatsDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
<el-dialog title="报表导出" :visible.sync="exportDialogVisible" width="480px" @close="resetExportForm">
<el-form ref="exportFormRef" :model="exportForm" :rules="exportRules" label-width="120px">
<el-form-item label="报表日期" prop="reportDate">
@@ -415,11 +415,11 @@ function buildDeptTradeStatsTree(flatRows, departmentRows) {
childrenByParent[pid].sort((x, y) =>
compareDeptAmtDesc(statsByUserId[y].totalDiffAmt, statsByUserId[x].totalDiffAmt))
})
function buildNode(uid) {
function buildNode(uid, depth) {
const row = statsByUserId[uid]
const childIds = childrenByParent[uid] || []
const kids = childIds.map(buildNode)
const node = { ...row }
const kids = childIds.map((cid) => buildNode(cid, depth + 1))
const node = { ...row, deptStatsDepth: depth }
if (kids.length) node.children = kids
return node
}
@@ -429,7 +429,7 @@ function buildDeptTradeStatsTree(flatRows, departmentRows) {
if (!p || !userIds.has(p)) roots.push(uid)
})
roots.sort((a, b) => compareDeptAmtDesc(statsByUserId[b].totalDiffAmt, statsByUserId[a].totalDiffAmt))
return roots.map(buildNode)
return roots.map((uid) => buildNode(uid, 0))
}
export default {
@@ -443,7 +443,7 @@ export default {
exportLoading: false,
checkUnsoldLoading: false,
deptStatsLoading: false,
deptStatsDialogVisible: false,
activeTab: 'list',
deptStatsForm: {
startDate: '',
endDate: ''
@@ -504,10 +504,53 @@ export default {
}
}
},
computed: {
deptStatsTableHeight() {
return 'calc(100vh - 280px)'
}
},
created() {
this.fetchList()
},
methods: {
deptStatsRowDepth(row) {
const d = row && row.deptStatsDepth != null ? Number(row.deptStatsDepth) : 0
if (!Number.isFinite(d) || d < 0) return 0
return Math.min(d, 8)
},
deptStatsRowClassName({ row }) {
const level = this.deptStatsRowDepth(row)
return `dept-stats-tree-row dept-stats-tree-level-${level}`
},
deptStatsLevelIconClass(row) {
const level = this.deptStatsRowDepth(row)
const icons = [
'el-icon-office-building',
'el-icon-s-custom',
'el-icon-user',
'el-icon-connection',
'el-icon-share',
'el-icon-remove-outline'
]
return icons[Math.min(level, icons.length - 1)]
},
handleMainTabClick(tab) {
if (!tab || tab.name !== 'deptStats') return
const sd = this.extractDateOnly(this.queryParams.reportStartTime)
const ed = this.extractDateOnly(this.queryParams.reportEndTime)
this.deptStatsForm = {
startDate: sd || '',
endDate: ed || ''
}
this.deptStatsTreeData = []
this.deptStatsSummaryText = ''
this.$nextTick(() => {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (tenantId && this.deptStatsForm.startDate && this.deptStatsForm.endDate) {
this.fetchDeptStatsTree()
}
})
},
fetchList() {
this.loading = true
getLbDailyUserTradeReportList(this.buildListParams())
@@ -663,28 +706,6 @@ export default {
if (this.$refs.exportFormRef) this.$refs.exportFormRef.clearValidate()
})
},
openDeptStatsDialog() {
const sd = this.extractDateOnly(this.queryParams.reportStartTime)
const ed = this.extractDateOnly(this.queryParams.reportEndTime)
this.deptStatsForm = {
startDate: sd || '',
endDate: ed || ''
}
this.deptStatsTreeData = []
this.deptStatsSummaryText = ''
this.deptStatsDialogVisible = true
this.$nextTick(() => {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (tenantId && this.deptStatsForm.startDate && this.deptStatsForm.endDate) {
this.fetchDeptStatsTree()
}
})
},
resetDeptStatsDialog() {
this.deptStatsForm = { startDate: '', endDate: '' }
this.deptStatsTreeData = []
this.deptStatsSummaryText = ''
},
fetchAllLbDepartmentUsers() {
const pageSize = 500
let all = []
@@ -984,31 +1005,180 @@ export default {
text-align: right;
}
}
}
</style>
<style lang="scss">
/* el-dialog 挂载到 body需非 scoped */
.dept-stats-dialog .dept-stats-filter {
.daily-report-tabs ::v-deep .el-tabs__header {
margin-bottom: 16px;
}
.dept-stats-panel {
.dept-stats-filter {
margin-bottom: 8px;
}
.dept-stats-dialog .dept-stats-summary {
.dept-stats-summary {
margin: 0 0 12px;
color: #606266;
font-size: 13px;
line-height: 20px;
}
.dept-stats-dialog .dept-stats-table-wrap {
.dept-stats-table-wrap {
width: 100%;
overflow-x: auto;
}
.dept-stats-dialog .dept-stats-user-id {
display: block;
margin-top: 2px;
.dept-stats-name-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
}
.dept-stats-level-icon {
flex-shrink: 0;
line-height: 1;
}
.dept-stats-name-text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dept-stats-user-id {
flex: 0 1 auto;
min-width: 0;
margin-left: 4px;
color: #909399;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dept-stats-tree-table ::v-deep td:first-child .cell {
white-space: nowrap;
}
/* 树表格层级:背景、左边线、整行字号与颜色 */
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-row td {
transition: background-color 0.15s ease;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-row:hover td {
background-color: #f0f7ff !important;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-0 td:first-child {
border-left: 4px solid #409eff;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-0 td {
background-color: #ecf5ff !important;
font-size: 14px;
font-weight: 600;
color: #303133;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-0 .dept-stats-level-icon {
color: #409eff;
font-size: 17px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-1 td:first-child {
border-left: 4px solid #67c23a;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-1 td {
background-color: #f0f9eb !important;
font-size: 13px;
font-weight: 500;
color: #434343;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-1 .dept-stats-level-icon {
color: #67c23a;
font-size: 16px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-2 td:first-child {
border-left: 4px solid #e6a23c;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-2 td {
background-color: #fdf6ec !important;
font-size: 13px;
font-weight: 400;
color: #606266;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-2 .dept-stats-level-icon {
color: #e6a23c;
font-size: 15px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-3 td:first-child {
border-left: 4px solid #909399;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-3 td {
background-color: #f4f4f5 !important;
font-size: 13px;
font-weight: 400;
color: #606266;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-3 .dept-stats-level-icon {
color: #909399;
font-size: 14px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-4 td:first-child {
border-left: 4px solid #c0c4cc;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-4 td {
background-color: #fafafa !important;
font-size: 12px;
color: #787b80;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-4 .dept-stats-level-icon {
color: #c0c4cc;
font-size: 14px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-5 td:first-child,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-6 td:first-child,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-7 td:first-child,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-8 td:first-child {
border-left: 4px solid #dcdfe6;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-5 td,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-6 td,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-7 td,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-8 td {
background-color: #ffffff !important;
font-size: 12px;
color: #909399;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-5 .dept-stats-level-icon,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-6 .dept-stats-level-icon,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-7 .dept-stats-level-icon,
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-level-8 .dept-stats-level-icon {
color: #c0c4cc;
font-size: 13px;
}
.dept-stats-tree-table ::v-deep tbody tr.dept-stats-tree-row .dept-stats-user-id {
font-weight: 400;
}
}
}
</style>