新增待办信息

This commit is contained in:
2026-01-01 18:10:00 +08:00
parent 7a44cf1eea
commit 971e3d86f1
11 changed files with 361 additions and 83 deletions

View File

@@ -134,13 +134,13 @@ public class vDate {
return switch (cycleType) { return switch (cycleType) {
case "D" -> case "D" ->
// 日最近30天格式 yyyy-MM-dd // 日最近30天格式 yyyy-MM-dd
baseDate.minusDays(7).format(DAY_FORMATTER); baseDate.minusDays(14).format(DAY_FORMATTER);
case "M" -> case "M" ->
// 月最近6个月格式 yyyy-MM // 月最近6个月格式 yyyy-MM
baseDate.minusMonths(6).format(MONTH_FORMATTER); baseDate.minusMonths(12).format(MONTH_FORMATTER);
case "Q" -> case "Q" ->
// 季度最近3年格式 yyyy-Qx // 季度最近3年格式 yyyy-Qx
getQuarterCycleCode(baseDate.minusYears(2)); getQuarterCycleCode(baseDate.minusYears(3));
case "Y" -> case "Y" ->
// 年最近6年格式 yyyy // 年最近6年格式 yyyy
String.valueOf(baseDate.minusYears(6).getYear()); String.valueOf(baseDate.minusYears(6).getYear());

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

View File

@@ -5,34 +5,64 @@
总金额<span class="amount-value">{{ totalAmountText }}</span> 总金额<span class="amount-value">{{ totalAmountText }}</span>
</div> </div>
</template> </template>
<div ref="chartDom" style="width: 100%; height: 300px;"></div> <div class="layout-container">
<div class="left-panel">
<div class="stat-card">
<span class="stat-title">本月收入</span>
<div class="stat-content">
<Icon icon="icons/erp-income.png" size="36"/>
<span class="stat-value">{{ thisIncome }} </span>
</div>
</div>
<div class="stat-card">
<span class="stat-title">本月支出</span>
<div class="stat-content">
<Icon icon="icons/erp-expense.png" size="36"/>
<span class="stat-value">{{ thisExpense }} </span>
</div>
</div>
<div class="stat-card">
<span class="stat-title">消费占比</span>
<div class="stat-content">
<Icon icon="icons/erp-share.png" size="36"/>
<span class="stat-value">{{ thisShare }} %</span>
</div>
</div>
</div>
<div class="right-panel">
<div ref="chartDom" class="chart-container"></div>
</div>
</div>
</Card> </Card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import { Card } from 'ant-design-vue'; import { Card } from 'ant-design-vue';
import { Icon } from '@jeesite/core/components/Icon';
import { ErpAccount, erpAccountListAll } from '@jeesite/erp/api/erp/account'; import { ErpAccount, erpAccountListAll } from '@jeesite/erp/api/erp/account';
import { ErpIncExpRatio, erpIncExpRatioListAll } from '@jeesite/erp/api/erp/incExpRatio';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import type { ECharts, EChartsOption, LegendSelectedChangedParams } from 'echarts'; import type { ECharts, EChartsOption } from 'echarts';
const listAccount = ref<ErpAccount[]>([]); const listAccount = ref<ErpAccount[]>([]);
const chartDom = ref<HTMLDivElement | null>(null); const chartDom = ref<HTMLDivElement | null>(null);
let myChart: ECharts | null = null; let myChart: ECharts | null = null;
// 核心修复1新增「是否全取消」的标记 + 维护所有图例名称的集合 const thisIncome = ref(0);
const allLegendNames = ref<Set<string>>(new Set()); // 存储所有账户名称(图例名) const thisExpense = ref(0);
const selectedLegends = ref<Set<string>>(new Set()); // 选中的图例 const thisShare = ref(0);
const isAllUnselected = ref(false); // 标记是否所有图例都被取消选中
// 修复后的总金额计算逻辑 // 核心标记:是否全取消 + 维护所有图例名称的集合
const allLegendNames = ref<Set<string>>(new Set());
const selectedLegends = ref<Set<string>>(new Set());
const isAllUnselected = ref(false);
// 总金额计算逻辑
const totalAmountText = computed(() => { const totalAmountText = computed(() => {
// 1. 所有图例都取消选中时,显示 0 元
if (isAllUnselected.value) { if (isAllUnselected.value) {
return '0.00'; return '0.00';
} }
// 2. 初始状态(全选):显示全部总金额
if (selectedLegends.value.size === 0 && !isAllUnselected.value) { if (selectedLegends.value.size === 0 && !isAllUnselected.value) {
const total = listAccount.value.reduce( const total = listAccount.value.reduce(
(sum, item) => sum + (Number(item.currentBalance) || 0), (sum, item) => sum + (Number(item.currentBalance) || 0),
@@ -41,7 +71,6 @@ const totalAmountText = computed(() => {
return total.toFixed(2); return total.toFixed(2);
} }
// 3. 部分选中:显示选中账户的合计
const total = listAccount.value const total = listAccount.value
.filter(item => selectedLegends.value.has(item.accountName || '未知账户')) .filter(item => selectedLegends.value.has(item.accountName || '未知账户'))
.reduce((sum, item) => sum + (Number(item.currentBalance) || 0), 0); .reduce((sum, item) => sum + (Number(item.currentBalance) || 0), 0);
@@ -53,6 +82,7 @@ const formatPercent = (value: number) => {
return `${value.toFixed(1)}%`; return `${value.toFixed(1)}%`;
}; };
// 优化颜色生成逻辑,增加颜色复用规则
const generateRandomColors = (count: number): string[] => { const generateRandomColors = (count: number): string[] => {
const colorLibrary = [ const colorLibrary = [
'#1890ff', '#52c41a', '#f5a623', '#fa8c16', '#722ed1', '#eb2f96', '#1890ff', '#52c41a', '#f5a623', '#fa8c16', '#722ed1', '#eb2f96',
@@ -61,20 +91,37 @@ const generateRandomColors = (count: number): string[] => {
]; ];
if (count <= colorLibrary.length) { if (count <= colorLibrary.length) {
const shuffled = [...colorLibrary].sort(() => 0.5 - Math.random()); return colorLibrary.slice(0, count);
return shuffled.slice(0, count);
} }
const colors: string[] = []; const colors: string[] = [...colorLibrary];
for (let i = 0; i < count; i++) { while (colors.length < count) {
const hue = Math.floor(Math.random() * 360); const hue = Math.floor(Math.random() * 360);
const saturation = 70 + Math.floor(Math.random() * 20); colors.push(`hsl(${hue}, 75%, 55%)`);
const lightness = 45 + Math.floor(Math.random() * 15);
colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
} }
return colors; return colors;
}; };
const getThisData = async() => {
try {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const formattedMonth = month.toString().padStart(2, '0');
const currentMonthStr = `${year}-${formattedMonth}`;
const params = {
cycleType: 'M',
statDate: currentMonthStr ,
}
const result = await erpIncExpRatioListAll(params);
thisIncome.value = result[0]?.incomeAmount || 0;
thisExpense.value = result[0]?.expenseAmount || 0;
thisShare.value = result[0]?.expenseRatio || 0;
} catch (error) {
console.error('获取数据失败:', error);
}
}
const fetchList = async () => { const fetchList = async () => {
try { try {
const result = await erpAccountListAll(); const result = await erpAccountListAll();
@@ -85,21 +132,18 @@ const fetchList = async () => {
} }
}; };
// 核心修复2重构图例选中事件处理逻辑 // 图例选中事件处理
const handleLegendSelect = (params: LegendSelectedChangedParams) => { const handleLegendSelect = (params: any) => {
const { name, selected } = params; const { name, selected } = params;
// 1. 更新选中集合
if (selected[name]) { if (selected[name]) {
selectedLegends.value.add(name); selectedLegends.value.add(name);
} else { } else {
selectedLegends.value.delete(name); selectedLegends.value.delete(name);
} }
// 2. 判断是否所有图例都被取消选中
isAllUnselected.value = selectedLegends.value.size === 0 && allLegendNames.value.size > 0; isAllUnselected.value = selectedLegends.value.size === 0 && allLegendNames.value.size > 0;
// 同步更新图表数据
// 3. 强制更新图表(确保饼图和总金额同步)
myChart?.setOption({ myChart?.setOption({
series: [ series: [
{ {
@@ -113,6 +157,7 @@ const handleLegendSelect = (params: LegendSelectedChangedParams) => {
}); });
}; };
// 初始化图表(适配新布局)
const initChart = () => { const initChart = () => {
if (!chartDom.value || listAccount.value.length === 0) return; if (!chartDom.value || listAccount.value.length === 0) return;
@@ -130,9 +175,7 @@ const initChart = () => {
const validData = pieData.filter(item => item.value > 0); const validData = pieData.filter(item => item.value > 0);
const colors = generateRandomColors(validData.length); const colors = generateRandomColors(validData.length);
// 核心修复3初始化所有图例名称集合
allLegendNames.value = new Set(validData.map(item => item.name)); allLegendNames.value = new Set(validData.map(item => item.name));
const total = validData.reduce((sum, item) => sum + item.value, 0); const total = validData.reduce((sum, item) => sum + item.value, 0);
const option: EChartsOption = { const option: EChartsOption = {
@@ -206,10 +249,9 @@ const initChart = () => {
}; };
myChart.setOption(option); myChart.setOption(option);
myChart.on('legendselectchanged', handleLegendSelect); myChart.on('legendselectchanged', handleLegendSelect);
// 初始化选中状态:全选 + 标记非全取消 // 初始化选中状态
selectedLegends.value = new Set(validData.map(item => item.name)); selectedLegends.value = new Set(validData.map(item => item.name));
isAllUnselected.value = false; isAllUnselected.value = false;
}; };
@@ -220,7 +262,11 @@ const resizeChart = () => {
onMounted(async () => { onMounted(async () => {
await fetchList(); await fetchList();
initChart(); await getThisData();
// 增加微延迟确保DOM渲染完成
setTimeout(() => {
initChart();
}, 100);
window.addEventListener('resize', resizeChart); window.addEventListener('resize', resizeChart);
}); });
@@ -254,6 +300,106 @@ onUnmounted(() => {
font-weight: 600; font-weight: 600;
} }
/* 核心布局样式 */
.layout-container {
display: flex;
width: 100%;
height: 300px;
gap: 16px;
padding: 8px 0;
}
/* 左侧20%面板 - 精简版 */
.left-panel {
width: 15%;
height: 100%;
border-radius: 8px;
padding: 8px;
overflow-y: auto;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 8px; /* 缩小卡片间距 */
}
/* 统计卡片样式 - 更小尺寸 */
.stat-card {
background-color: #f0f7ff; /* 淡蓝色背景 */
border-radius: 6px;
padding: 10px 8px; /* 缩小内边距 */
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
border: 1px solid #e6f4ff;
}
/* 统计标题 - 简化为span */
.stat-title {
font-size: 13px;
font-weight: 600;
color: #1f2937;
margin-bottom: 8px; /* 缩小间距 */
text-align: left;
line-height: 1.2;
}
/* 统计内容(图标+金额)- 关键优化 */
.stat-content {
display: flex;
align-items: center;
justify-content: space-between; /* 改为两端对齐 */
width: 100%; /* 确保占满容器宽度 */
padding: 0 4px; /* 增加左右内边距 */
}
/* 图标容器 - 确保图标靠左对齐 */
:deep(.stat-content .icon) {
display: flex;
align-items: center;
justify-content: flex-start;
flex-shrink: 0; /* 防止图标被压缩 */
}
/* 统计数值 - 确保数字靠右对齐 */
.stat-value {
font-size: 12px; /* 缩小字体 */
font-weight: 600;
color: #1890ff;
white-space: nowrap;
text-align: right; /* 文字右对齐 */
flex-shrink: 0; /* 防止数字被压缩 */
}
/* 右侧80%图表面板 */
.right-panel {
width: 85%;
height: 100%;
position: relative;
}
.chart-container {
width: 100%;
height: 100%;
border-radius: 8px;
}
/* 滚动条样式优化 */
:deep(.left-panel::-webkit-scrollbar) {
width: 4px; /* 缩小滚动条 */
}
:deep(.left-panel::-webkit-scrollbar-thumb) {
background: #d9d9d9;
border-radius: 2px;
}
:deep(.left-panel::-webkit-scrollbar-track) {
background: #f5f5f5;
}
/* 图表样式优化 */
:deep(.ant-card .echarts-legend-item) { :deep(.ant-card .echarts-legend-item) {
margin: 0 8px !important; margin: 0 8px !important;
padding: 0 5px; padding: 0 5px;
@@ -278,4 +424,40 @@ onUnmounted(() => {
font-size: 14px; font-size: 14px;
color: #999; color: #999;
} }
/* 响应式适配 */
@media (max-width: 768px) {
.layout-container {
flex-direction: column;
height: auto;
}
.left-panel, .right-panel {
width: 100%;
}
.left-panel {
flex-direction: row;
height: 140px; /* 缩小高度 */
overflow-x: auto;
padding: 6px;
}
.stat-card {
min-width: 120px; /* 缩小最小宽度 */
flex: none;
padding: 8px 6px;
}
.right-panel {
height: 300px;
margin-top: 12px;
}
/* 移动端保持图标左、数字右对齐 */
.stat-content {
justify-content: space-between;
padding: 0 2px;
}
}
</style> </style>

View File

@@ -1,69 +1,137 @@
<template> <template>
<PageWrapper class="dashboard-container"> <PageWrapper class="dashboard-container">
<template #extra> <div class="top-header-section">
<BasicForm <div class="header-icon-wrapper">
:labelWidth="100" <img :src="headerImg" class="header-img" />
:schemas="schemaForm.schemas" </div>
style="width: 245px;" <BasicForm
/> :labelWidth="100"
</template> :schemas="schemaForm.schemas"
class="search-form"
/>
</div>
<div class="two-column-layout"> <div class="two-column-layout">
<ChartPie /> <ChartPie />
<ChartLineRatio :formParams="FormValues" /> <ChartLineRatio :formParams="FormValues" />
</div> </div>
<div class="two-column-layout"> <div class="two-column-layout">
<ChartBarCycle :formParams="FormValues" /> <ChartBarCycle :formParams="FormValues" />
<ChartBarAccount :formParams="FormValues" /> <ChartBarAccount :formParams="FormValues" />
</div> </div>
<ChartLineType :formParams="FormValues" /> <ChartLineType :formParams="FormValues" />
<div class="two-column-layout"> <div class="two-column-layout">
<ChartLineExp :formParams="FormValues" /> <ChartLineExp :formParams="FormValues" />
<ChartLineInc :formParams="FormValues" /> <ChartLineInc :formParams="FormValues" />
</div> </div>
</PageWrapper> </PageWrapper>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import { Card } from 'ant-design-vue'; import { Card } from 'ant-design-vue';
import { BasicForm, FormSchema, FormProps } from '@jeesite/core/components/Form'; import { Icon } from '@jeesite/core/components/Icon';
import { PageWrapper } from '@jeesite/core/components/Page'; import { BasicForm, FormSchema, FormProps } from '@jeesite/core/components/Form';
import ChartPie from './components/ChartPie.vue'; import { PageWrapper } from '@jeesite/core/components/Page';
import ChartLineExp from './components/ChartLineExp.vue'; import ChartPie from './components/ChartPie.vue';
import ChartLineInc from './components/ChartLineInc.vue'; import ChartLineExp from './components/ChartLineExp.vue';
import ChartBarCycle from './components/ChartBarCycle.vue'; import ChartLineInc from './components/ChartLineInc.vue';
import ChartBarAccount from './components/ChartBarAccount.vue'; import ChartBarCycle from './components/ChartBarCycle.vue';
import ChartLineType from './components/ChartLineType.vue'; import ChartBarAccount from './components/ChartBarAccount.vue';
import ChartLineRatio from './components/ChartLineRatio.vue'; import ChartLineType from './components/ChartLineType.vue';
import ChartLineRatio from './components/ChartLineRatio.vue';
const FormValues = ref<Record<string, any>>({
cycleType: 'M'
});
const schemaForm: FormProps = { import headerImg from '@jeesite/assets/images/bigview.png';
baseColProps: { md: 8, lg: 6 },
labelWidth: 90, const FormValues = ref<Record<string, any>>({
schemas: [ cycleType: 'M'
{ });
label: '周期',
field: 'cycleType', const schemaForm: FormProps = {
defaultValue: 'M', baseColProps: { md: 8, lg: 6 },
component: 'Select', labelWidth: 90,
componentProps: { schemas: [
dictType: 'report_cycle', {
allowClear: true, label: '周期',
onChange: (value: string) => { field: 'cycleType',
FormValues.value.cycleType = value || ''; defaultValue: 'M',
} component: 'Select',
}, componentProps: {
colProps: { md: 24, lg: 24 }, dictType: 'report_cycle',
}, allowClear: true,
], onChange: (value: string) => {
}; FormValues.value.cycleType = value || '';
}
},
colProps: { md: 24, lg: 24 },
},
],
};
</script> </script>
<style scoped> <style scoped>
.dashboard-container { .dashboard-container {
padding: 0 8px; padding: 0 8px;
min-height: 100vh;
}
.top-header-section {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
width: 100%;
background-color: #e6f7ff;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
border: 1px solid #bde5ff;
}
/* 优化:图标容器样式 */
.header-icon-wrapper {
display: flex;
align-items: center;
gap: 8px; /* 图标与文本间距 */
font-size: 16px;
font-weight: 500;
transition: all 0.2s ease;
}
/* 核心优化:限制图片尺寸 */
.header-img {
width: 60px; /* 固定宽度 */
height: 30px; /* 固定高度 */
object-fit: contain; /* 保持图片比例,不拉伸 */
flex-shrink: 0; /* 防止被压缩 */
border-radius: 4px; /* 轻微圆角,更美观 */
}
/* 优化:图标核心样式 */
.dashboard-icon {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
transition: transform 0.2s ease, filter 0.2s ease;
background: transparent;
}
/* hover效果 */
.header-icon-wrapper:hover .header-img {
transform: scale(1.05);
filter: brightness(1.1);
}
/* 图标文本样式 */
.icon-text {
color: #1890ff;
margin-left: 4px;
white-space: nowrap; /* 防止文本换行 */
}
/* 右侧表单样式 */
.search-form {
width: 245px;
} }
.two-column-layout { .two-column-layout {
@@ -71,6 +139,7 @@
gap: 12px; gap: 12px;
width: 100%; width: 100%;
padding: 0; padding: 0;
margin-bottom: 12px; /* 增加图表区域间距 */
} }
.chart-line-wrapper { .chart-line-wrapper {
@@ -79,12 +148,39 @@
padding: 0; padding: 0;
} }
/* 响应式适配(可选) */ /* 响应式适配优化 */
@media (max-width: 768px) { @media (max-width: 768px) {
.two-column-layout { .two-column-layout {
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
margin-bottom: 4px; /* 移动端也保持紧凑 */ margin-bottom: 8px;
}
.top-header-section {
flex-direction: column;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
}
.search-form {
width: 100%;
}
/* 移动端图片适配 */
.header-img {
width: 32px;
height: 32px;
}
.header-icon-wrapper {
font-size: 14px;
} }
} }
</style>
/* 额外优化:防止图片溢出 */
:deep(.header-img) {
max-width: 100%;
height: auto;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB