部门交易金额代码框架
This commit is contained in:
@@ -83,3 +83,17 @@ export function listYestodayBuyPositiveAndDailySellZero(startDate, endDate, tena
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 按日期范围统计本人及递归下级交易金额(不含 report_sum) */
|
||||
export function statsUserTradeWithTeamByDateRangeAndTenant(startDate, endDate, tenantId) {
|
||||
return request({
|
||||
url: '/lbDailyUserTradeReport/stats/user-trade-with-team/by-date-range-and-tenant',
|
||||
method: 'get',
|
||||
params: {
|
||||
startDate,
|
||||
endDate,
|
||||
tenantId
|
||||
},
|
||||
timeout: 120000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<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 应付(当前人))
|
||||
@@ -215,6 +216,99 @@
|
||||
</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">
|
||||
@@ -244,8 +338,10 @@ import {
|
||||
exportLbDailyUserTradeReportByDateAndTenant,
|
||||
getLbDailyUserTradeReportList,
|
||||
listYestodayBuyPositiveAndDailySellZero,
|
||||
statsUserTradeWithTeamByDateRangeAndTenant,
|
||||
updateLbDailyUserTradeReport
|
||||
} from '@/api/lb-daily-user-trade-report'
|
||||
import { getLbDepartmentUserList } from '@/api/lb-department-user'
|
||||
import { getBusinessHeaders } from '@/utils/business-headers'
|
||||
|
||||
function formatDateTimeForQuery(date) {
|
||||
@@ -273,6 +369,69 @@ function getLocalTodayDateString() {
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function compareDeptAmtDesc(a, b) {
|
||||
const na = Number(a)
|
||||
const nb = Number(b)
|
||||
if (!Number.isFinite(na) && !Number.isFinite(nb)) return 0
|
||||
if (!Number.isFinite(na)) return 1
|
||||
if (!Number.isFinite(nb)) return -1
|
||||
return nb - na
|
||||
}
|
||||
|
||||
/** 将统计扁平行按 lbDepartmentUser 的 parentId 转为 el-table 树形数据 */
|
||||
function buildDeptTradeStatsTree(flatRows, departmentRows) {
|
||||
if (!Array.isArray(flatRows) || flatRows.length === 0) return []
|
||||
const userIds = new Set()
|
||||
flatRows.forEach((r) => {
|
||||
if (r && r.userId != null && String(r.userId).trim() !== '') userIds.add(String(r.userId).trim())
|
||||
})
|
||||
const parentByUserId = {}
|
||||
if (Array.isArray(departmentRows)) {
|
||||
departmentRows.forEach((d) => {
|
||||
if (!d || d.userId == null || String(d.userId).trim() === '') return
|
||||
const uid = String(d.userId).trim()
|
||||
if (!userIds.has(uid)) return
|
||||
const rawParent = d.parentId == null ? '' : String(d.parentId).trim()
|
||||
if (!(uid in parentByUserId)) parentByUserId[uid] = rawParent
|
||||
})
|
||||
}
|
||||
const statsByUserId = {}
|
||||
flatRows.forEach((r) => {
|
||||
if (!r || r.userId == null) return
|
||||
const uid = String(r.userId).trim()
|
||||
statsByUserId[uid] = { ...r, userId: uid }
|
||||
})
|
||||
const childrenByParent = {}
|
||||
userIds.forEach((uid) => {
|
||||
childrenByParent[uid] = []
|
||||
})
|
||||
userIds.forEach((uid) => {
|
||||
const p = parentByUserId[uid] != null ? String(parentByUserId[uid]).trim() : ''
|
||||
if (p && userIds.has(p)) {
|
||||
childrenByParent[p].push(uid)
|
||||
}
|
||||
})
|
||||
Object.keys(childrenByParent).forEach((pid) => {
|
||||
childrenByParent[pid].sort((x, y) =>
|
||||
compareDeptAmtDesc(statsByUserId[y].totalDiffAmt, statsByUserId[x].totalDiffAmt))
|
||||
})
|
||||
function buildNode(uid) {
|
||||
const row = statsByUserId[uid]
|
||||
const childIds = childrenByParent[uid] || []
|
||||
const kids = childIds.map(buildNode)
|
||||
const node = { ...row }
|
||||
if (kids.length) node.children = kids
|
||||
return node
|
||||
}
|
||||
const roots = []
|
||||
userIds.forEach((uid) => {
|
||||
const p = parentByUserId[uid] != null ? String(parentByUserId[uid]).trim() : ''
|
||||
if (!p || !userIds.has(p)) roots.push(uid)
|
||||
})
|
||||
roots.sort((a, b) => compareDeptAmtDesc(statsByUserId[b].totalDiffAmt, statsByUserId[a].totalDiffAmt))
|
||||
return roots.map(buildNode)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'LbDailyReport',
|
||||
data() {
|
||||
@@ -283,6 +442,14 @@ export default {
|
||||
reportSumLoading: false,
|
||||
exportLoading: false,
|
||||
checkUnsoldLoading: false,
|
||||
deptStatsLoading: false,
|
||||
deptStatsDialogVisible: false,
|
||||
deptStatsForm: {
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
},
|
||||
deptStatsTreeData: [],
|
||||
deptStatsSummaryText: '',
|
||||
list: [],
|
||||
total: 0,
|
||||
dialogVisible: false,
|
||||
@@ -496,6 +663,93 @@ 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 = []
|
||||
let current = 1
|
||||
let total = Number.POSITIVE_INFINITY
|
||||
const step = () =>
|
||||
getLbDepartmentUserList({ current, size: pageSize }).then((res) => {
|
||||
const ok = res && (res.code === 20000 || res.code === 200 || res.success === true)
|
||||
const chunk = ok && Array.isArray(res.data) ? res.data : []
|
||||
if (typeof res.total === 'number') total = res.total
|
||||
else if (res.page && typeof res.page.total === 'number') total = res.page.total
|
||||
all = all.concat(chunk)
|
||||
current += 1
|
||||
if (chunk.length === 0 || chunk.length < pageSize || all.length >= total) return all
|
||||
return step()
|
||||
})
|
||||
return step()
|
||||
},
|
||||
fetchDeptStatsTree() {
|
||||
const tenantId = this.getTenantIdFromBusinessHeaders()
|
||||
if (!tenantId) {
|
||||
this.$message.error('未获取到租户ID,请先登录或切换到正确租户后重试')
|
||||
return
|
||||
}
|
||||
const { startDate, endDate } = this.deptStatsForm
|
||||
if (!startDate || !endDate) {
|
||||
this.$message.warning('请选择开始日期和结束日期')
|
||||
return
|
||||
}
|
||||
this.deptStatsLoading = true
|
||||
this.deptStatsTreeData = []
|
||||
this.deptStatsSummaryText = ''
|
||||
Promise.all([
|
||||
statsUserTradeWithTeamByDateRangeAndTenant(startDate, endDate, tenantId),
|
||||
this.fetchAllLbDepartmentUsers()
|
||||
])
|
||||
.then(([statsRes, deptList]) => {
|
||||
const ok = statsRes && (statsRes.code === 20000 || statsRes.code === 200 || statsRes.success === true)
|
||||
if (!ok) {
|
||||
this.$message.error((statsRes && statsRes.message) || '统计失败')
|
||||
return
|
||||
}
|
||||
const rows = Array.isArray(statsRes.data) ? statsRes.data : []
|
||||
const total = typeof statsRes.total === 'number' ? statsRes.total : rows.length
|
||||
this.deptStatsTreeData = buildDeptTradeStatsTree(rows, deptList)
|
||||
const rangeLabel = `${startDate} ~ ${endDate}`
|
||||
this.deptStatsSummaryText =
|
||||
`统计区间:${rangeLabel};租户:${tenantId};共 ${total} 人(树按部门上下级展示,同级按合计差额降序)`
|
||||
if (!rows.length) {
|
||||
this.$message.success((statsRes && statsRes.message) || '暂无数据')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.deptStatsTreeData = []
|
||||
})
|
||||
.finally(() => {
|
||||
this.deptStatsLoading = false
|
||||
})
|
||||
},
|
||||
formatDeptAmt(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
const n = Number(val)
|
||||
if (!Number.isFinite(n)) return String(val)
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
},
|
||||
submitDeleteByDate() {
|
||||
this.$refs.deleteByDateFormRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
@@ -732,3 +986,29 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* el-dialog 挂载到 body,需非 scoped */
|
||||
.dept-stats-dialog .dept-stats-filter {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dept-stats-dialog .dept-stats-summary {
|
||||
margin: 0 0 12px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.dept-stats-dialog .dept-stats-table-wrap {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.dept-stats-dialog .dept-stats-user-id {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user