项目初始化

This commit is contained in:
2026-03-23 18:18:16 +08:00
parent dc5a92880e
commit cc4bb9188b
5 changed files with 348 additions and 285 deletions

View File

@@ -119,6 +119,52 @@ public class ErpScreenController {
return chartDataItems; return chartDataItems;
} }
/**
* 季度收支
*/
@RequestMapping(value = "getErpQuarterChart")
@ResponseBody
public List<ChartDataItem> getErpQuarterChart(ErpTransactionFlow erpTransactionFlow) {
List<ChartDataItem> chartDataItems = new ArrayList<>();
List<ErpTransactionFlow> flowList = erpTransactionFlowService.findList(erpTransactionFlow);
Map<String, Map<String, Object>> accountMap = flowList.stream()
.collect(Collectors.groupingBy(
flow -> flow.getYearDate() + "Q" + (Integer.parseInt(flow.getMonthDate()) + 2) / 3,
Collectors.collectingAndThen(Collectors.toList(), list -> {
BigDecimal sumValue01 = list.stream()
.filter(flow -> flow.getFlowType().equals("2"))
.map(ErpTransactionFlow::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal sumValue02 = list.stream()
.filter(flow -> flow.getFlowType().equals("1"))
.map(ErpTransactionFlow::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal sumValue04 = BigDecimalUtils.subtract(sumValue01, sumValue02);
BigDecimal sumValue03 = BigDecimalUtils.percent(sumValue02, sumValue01);
return Map.of(
"sumValue01", sumValue01,
"sumValue02", sumValue02,
"sumValue03", sumValue03,
"sumValue04", sumValue04
);
})
));
List<Map.Entry<String, Map<String, Object>>> sortedList = new ArrayList<>(accountMap.entrySet());
sortedList.sort(Map.Entry.comparingByKey());
for (Map.Entry<String, Map<String, Object>> entry : sortedList) {
String quarter = entry.getKey();
Map<String, Object> map = entry.getValue();
ChartDataItem item = new ChartDataItem();
item.setAxisName(quarter);
item.setValue01(map.get("sumValue01").toString());
item.setValue02(map.get("sumValue02").toString());
item.setValue03(map.get("sumValue03").toString());
item.setValue04(map.get("sumValue04").toString());
chartDataItems.add(item);
}
return chartDataItems;
}
/** /**
* 分类收支 * 分类收支
@@ -151,12 +197,10 @@ public class ErpScreenController {
BigDecimal v2 = (BigDecimal) e2.getValue().get("sumValue02"); BigDecimal v2 = (BigDecimal) e2.getValue().get("sumValue02");
return v2.compareTo(v1); return v2.compareTo(v1);
}); });
long soring = 1;
for (Map.Entry<String, Map<String, Object>> entry : sortedList) { for (Map.Entry<String, Map<String, Object>> entry : sortedList) {
String categoryName = entry.getKey(); String categoryName = entry.getKey();
Map<String, Object> map = entry.getValue(); Map<String, Object> map = entry.getValue();
ChartDataItem item = new ChartDataItem(); ChartDataItem item = new ChartDataItem();
item.setSoring(soring++);
item.setAxisName(categoryName); item.setAxisName(categoryName);
item.setValue01(map.get("sumValue01").toString()); item.setValue01(map.get("sumValue01").toString());
item.setValue02(map.get("sumValue02").toString()); item.setValue02(map.get("sumValue02").toString());

View File

@@ -202,19 +202,19 @@
const expenseVal = index02Yuan[idx]; const expenseVal = index02Yuan[idx];
const netProfitVal = index03Yuan[idx]; const netProfitVal = index03Yuan[idx];
return ` return `
<div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div> <div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div>
<table style="width:100%; border-collapse:collapse; text-align:center"> <table style="width:100%; border-collapse:collapse; text-align:center">
<tr> <tr>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">净利润(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">净利润(元)</td>
</tr> </tr>
<tr> <tr>
<td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td> <td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td> <td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${netProfitVal}</td> <td style="border:1px solid #409EFF; padding:4px">${netProfitVal}</td>
</tr> </tr>
</table> </table>
`; `;
}; };

View File

@@ -237,21 +237,21 @@
const rateVal = index03[idx]; const rateVal = index03[idx];
const profitVal = index04Yuan[idx]; const profitVal = index04Yuan[idx];
return ` return `
<div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div> <div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div>
<table style="width:100%; border-collapse:collapse; text-align:center"> <table style="width:100%; border-collapse:collapse; text-align:center">
<tr> <tr>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">利润率(%)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">利润率(%)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">净利润(元)</td> <td style="border:1px solid #409EFF; padding:4px; font-weight:bold">净利润(元)</td>
</tr> </tr>
<tr> <tr>
<td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td> <td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td> <td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${rateVal}</td> <td style="border:1px solid #409EFF; padding:4px">${rateVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${profitVal}</td> <td style="border:1px solid #409EFF; padding:4px">${profitVal}</td>
</tr> </tr>
</table> </table>
`; `;
}; };

View File

@@ -7,280 +7,296 @@
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue' import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
import * as echarts from 'echarts' import * as echarts from 'echarts';
// import { getItemInfoList } from '@/api/bizApi' import { ChartDataItem, getErpQuarterChart } from '@jeesite/erp/api/erp/screen';
const props = defineProps({ const props = defineProps({
formParams: { formParams: {
type: Object, type: Object,
default: () => ({}) default: () => ({}),
} },
}); });
const vList = ref([]) const vList = ref<ChartDataItem[]>([]);
const chartRef = ref(null) const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance = null let chartInstance: echarts.ECharts | null = null;
const resizeHandler = () => chartInstance?.resize() const resizeHandler = () => chartInstance?.resize();
async function getList() { async function getList() {
try { try {
const params = { const params = {
...props.formParams, ...props.formParams,
itemCode: 'ERP_YEARQUAR_Y001', };
const res = await getErpQuarterChart(params);
vList.value = res || [];
} catch (error) {
console.error(error);
vList.value = [];
} }
const res = await getItemInfoList(params)
vList.value = res || []
} catch (error) {
console.error(error)
vList.value = []
}
}
function initChart() {
const el = chartRef.value
if (!el) return
if (!chartInstance) {
chartInstance = echarts.init(el)
} }
const baseOption = { const parseAmount = (value?: string | number) => {
tooltip: { const parsed = Number(value ?? 0);
trigger: 'axis', return Number.isFinite(parsed) ? parsed : 0;
axisPointer: { type: 'cross' }, };
backgroundColor: 'rgba(145, 200, 255, 0.9)',
borderColor: '#409EFF',
borderWidth: 1,
textStyle: { color: '#0a3b70' },
padding: [8, 12],
borderRadius: 6
},
legend: {
top: '10',
left: 'center',
textStyle: { fontSize: 12, color: '#e0e6ff' },
data: ['收入', '支出', '占比']
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '15%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: [],
axisLabel: {
fontSize: 11,
interval: 0,
color: '#b4c7e7'
},
axisLine: { lineStyle: { color: '#1a508b' } },
boundaryGap: true
}
],
yAxis: [
{
type: 'value',
name: '金额 (万元)',
nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value}', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } }
},
{
type: 'value',
name: '占比 (%)',
nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value} %', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } },
min: 'dataMin',
max: 'dataMax',
scale: true
}
],
series: [
{
name: '收入',
type: 'bar',
yAxisIndex: 0,
data: [],
barWidth: '12%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#85E868' },
{ offset: 1, color: '#67C23A' }
]),
borderRadius: [8, 8, 0, 0]
},
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#fff',
formatter: '{c}'
}
},
{
name: '支出',
type: 'bar',
yAxisIndex: 0,
data: [],
barWidth: '12%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#FF8A8A' },
{ offset: 1, color: '#F56C6C' }
]),
borderRadius: [8, 8, 0, 0]
},
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#fff',
formatter: '{c}'
}
},
{
name: '占比',
type: 'line',
yAxisIndex: 1,
data: [],
smooth: true,
lineStyle: { width: 1.5, color: '#409EFF' },
symbol: 'circle',
symbolSize: 5,
label: {
show: true,
position: 'outside',
fontSize: 10,
color: '#409EFF',
formatter: '{c}',
offset: [0, -5]
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.0)' }
])
},
emphasis: {
lineStyle: { width: 2 },
symbolSize: 7
}
}
]
}
if (vList.value.length > 0) { const parseRate = (value?: string | number) => {
const xData = vList.value.map(item => { const parsed = Number(value ?? 0);
const xaxis = item.xaxis || '' return Number.isFinite(parsed) ? Number(parsed.toFixed(2)) : 0;
return xaxis; };
})
const index01Yuan = vList.value.map(item => item.index01 || 0)
const index02Yuan = vList.value.map(item => item.index02 || 0)
const index03 = vList.value.map(item => item.index03 || 0)
const index01Wan = index01Yuan.map(val => (val / 10000).toFixed(2)) function initChart() {
const index02Wan = index02Yuan.map(val => (val / 10000).toFixed(2)) const el = chartRef.value;
if (!el) return;
baseOption.tooltip.formatter = function (params) { if (!chartInstance) {
const title = params[0].axisValue chartInstance = echarts.init(el);
const idx = params[0].dataIndex
const incomeVal = index01Yuan[idx]
const expenseVal = index02Yuan[idx]
const rateVal = index03[idx]
return `
<div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div>
<table style="width:100%; border-collapse:collapse; text-align:center">
<tr>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">占比(%)</td>
</tr>
<tr>
<td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${rateVal}</td>
</tr>
</table>
`
} }
baseOption.xAxis[0].data = xData const baseOption: echarts.EChartsOption & {
baseOption.series[0].data = index01Wan tooltip: echarts.TooltipComponentOption;
baseOption.series[1].data = index02Wan xAxis: Array<echarts.XAXisComponentOption & { data: string[] }>;
baseOption.series[2].data = index03 series: echarts.SeriesOption[];
} = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
backgroundColor: 'rgba(145, 200, 255, 0.9)',
borderColor: '#409EFF',
borderWidth: 1,
textStyle: { color: '#0a3b70' },
padding: [8, 12],
borderRadius: 6,
},
legend: {
top: '10',
left: 'center',
textStyle: { fontSize: 12, color: '#e0e6ff' },
data: ['收入', '支出', '占比'],
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '15%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: [],
axisLabel: {
fontSize: 11,
interval: 0,
color: '#b4c7e7',
},
axisLine: { lineStyle: { color: '#1a508b' } },
boundaryGap: true,
},
],
yAxis: [
{
type: 'value',
name: '金额 (万元)',
nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value}', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } },
},
{
type: 'value',
name: '占比 (%)',
nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value} %', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } },
min: 'dataMin',
max: 'dataMax',
scale: true,
},
],
series: [
{
name: '收入',
type: 'bar',
yAxisIndex: 0,
data: [],
barWidth: '12%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#85E868' },
{ offset: 1, color: '#67C23A' },
]),
borderRadius: [8, 8, 0, 0],
},
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#fff',
formatter: '{c}',
},
},
{
name: '支出',
type: 'bar',
yAxisIndex: 0,
data: [],
barWidth: '12%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#FF8A8A' },
{ offset: 1, color: '#F56C6C' },
]),
borderRadius: [8, 8, 0, 0],
},
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#fff',
formatter: '{c}',
},
},
{
name: '占比',
type: 'line',
yAxisIndex: 1,
data: [],
smooth: true,
lineStyle: { width: 1.5, color: '#409EFF' },
symbol: 'circle',
symbolSize: 5,
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#409EFF',
formatter: '{c}',
offset: [0, -5],
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.0)' },
]),
},
emphasis: {
focus: 'series',
},
},
],
};
if (vList.value.length > 0) {
const xData = vList.value.map((item) => {
const xaxis = item.axisName || '';
return xaxis;
});
const value01Yuan = vList.value.map((item) => parseAmount(item.value01));
const value02Yuan = vList.value.map((item) => parseAmount(item.value02));
const value03 = vList.value.map((item) => parseRate(item.value03));
const value01Wan = value01Yuan.map((val) => Number((val / 10000).toFixed(2)));
const value02Wan = value02Yuan.map((val) => Number((val / 10000).toFixed(2)));
baseOption.tooltip.formatter = function (params) {
const title = params[0].axisValue;
const idx = params[0].dataIndex;
const incomeVal = value01Yuan[idx];
const expenseVal = value02Yuan[idx];
const rateVal = value03[idx];
return `
<div style="text-align:center; font-weight:bold; margin-bottom:6px">${title}</div>
<table style="width:100%; border-collapse:collapse; text-align:center">
<tr>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">收入(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">支出(元)</td>
<td style="border:1px solid #409EFF; padding:4px; font-weight:bold">占比(%)</td>
</tr>
<tr>
<td style="border:1px solid #409EFF; padding:4px">${incomeVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${expenseVal}</td>
<td style="border:1px solid #409EFF; padding:4px">${rateVal}</td>
</tr>
</table>
`;
};
baseOption.xAxis[0].data = xData;
baseOption.series[0].data = value01Wan;
baseOption.series[1].data = value02Wan;
baseOption.series[2].data = value03;
}
chartInstance.setOption(baseOption, true);
} }
chartInstance.setOption(baseOption, true) watch(
} () => props.formParams,
() => {
getList();
},
{ deep: true, immediate: true },
);
watch( watch(
() => props.formParams, vList,
() => { () => {
getList() nextTick(() => initChart());
}, },
{ deep: true, immediate: true } { deep: true },
) );
watch(vList, () => { onMounted(() => {
nextTick(() => initChart()) initChart();
}, { deep: true }) window.addEventListener('resize', resizeHandler);
});
onMounted(() => { onUnmounted(() => {
initChart() window.removeEventListener('resize', resizeHandler);
window.addEventListener('resize', resizeHandler) if (chartInstance) {
}) chartInstance.dispose();
chartInstance = null;
onUnmounted(() => { }
window.removeEventListener('resize', resizeHandler) });
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script> </script>
<style scoped> <style scoped>
.chart-card { .chart-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
background: rgba(0, 0, 0, 0.1) url("@/assets/chart/box/16.png") no-repeat; background: rgba(0, 0, 0, 0.1) url('@jeesite/assets/chart/box/16.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
} }
.chart-card-header { .chart-card-header {
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
padding: 0 16px; padding: 0 16px;
background-color: rgba(26, 80, 139, 0.5); background-color: rgba(26, 80, 139, 0.5);
border-bottom: 1px solid #1a508b; border-bottom: 1px solid #1a508b;
display: flex; display: flex;
align-items: center; align-items: center;
background: rgba(0, 0, 0, 0.1) url("@/assets/chart/title/23.png") no-repeat; background: rgba(0, 0, 0, 0.1) url('@jeesite/assets/chart/title/23.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
} }
.chart-card-title { .chart-card-title {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #409EFF; color: #409eff;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
.bar-line-chart-container { .bar-line-chart-container {
flex: 1; flex: 1;
width: 100%; width: 100%;
height: calc(100% - 40px); height: calc(100% - 40px);
} }
</style> </style>

View File

@@ -60,3 +60,6 @@ export const CategoryChart = (params?: ErpTransactionFlow | any) =>
export const ErpAccountChart = (params?: ErpTransactionFlow | any) => export const ErpAccountChart = (params?: ErpTransactionFlow | any) =>
defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getErpAccountChart', params }); defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getErpAccountChart', params });
export const getErpQuarterChart = (params?: ErpTransactionFlow | any) =>
defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getErpQuarterChart', params });