项目初始化

This commit is contained in:
2026-03-23 17:38:35 +08:00
parent 876b3800b4
commit 6011e29abf
12 changed files with 1554 additions and 1108 deletions

View File

@@ -0,0 +1,124 @@
package com.jeesite.modules.apps.Module;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class ChartDataItem implements Serializable {
/**
* 项目编号
*/
private String itemCode;
/**
* 序号
*/
private Long soring;
/**
* X轴名称
*/
private String axisName;
/**
* 指标01
*/
private String value01;
/**
* 指标02
*/
private String value02;
/**
* 指标03
*/
private String value03;
/**
* 指标04
*/
private String value04;
/**
* 指标05
*/
private String value05;
/**
* 指标06
*/
private String value06;
/**
* 指标07
*/
private String value07;
/**
* 指标08
*/
private String value08;
/**
* 指标09
*/
private String value09;
/**
* 指标10
*/
private String value10;
/**
* 指标11
*/
private String value11;
/**
* 指标12
*/
private String value12;
/**
* 指标13
*/
private String value13;
/**
* 指标14
*/
private String value14;
/**
* 指标15
*/
private String value15;
/**
* 业务主键编号
*/
private String bizKey;
/**
* 指标求和
*/
private BigDecimal indexSum;
/**
* 指标平均
*/
private BigDecimal indexAvg;
/**
* 指标最大
*/
private BigDecimal indexMax;
/**
* 指标最小
*/
private BigDecimal indexMin;
}

View File

@@ -0,0 +1,168 @@
package com.jeesite.modules.apps.web;
import com.jeesite.modules.apps.Module.ChartDataItem;
import com.jeesite.modules.erp.entity.ErpTransactionFlow;
import com.jeesite.modules.erp.service.ErpTransactionFlowService;
import com.jeesite.modules.utils.BigDecimalUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller
@RequestMapping(value = "${adminPath}/erp/screen")
public class ErpScreenController {
private final ErpTransactionFlowService erpTransactionFlowService;
public ErpScreenController(ErpTransactionFlowService erpTransactionFlowService) {
this.erpTransactionFlowService = erpTransactionFlowService;
}
/**
* 账号收支
*/
@RequestMapping(value = "getErpAccountChart")
@ResponseBody
public List<ChartDataItem> getErpAccountChart(ErpTransactionFlow erpTransactionFlow) {
List<ChartDataItem> chartDataItems = new ArrayList<>();
List<ErpTransactionFlow> flowList = erpTransactionFlowService.findList(erpTransactionFlow);
Map<String, Map<String, Object>> accountMap = flowList.stream()
.collect(Collectors.groupingBy(
ErpTransactionFlow::getAccountName,
Collectors.collectingAndThen(Collectors.toList(), list -> {
String accountId = list.stream()
.findFirst()
.map(ErpTransactionFlow::getAccountId)
.orElse("");
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 sumValue03 = BigDecimalUtils.subtract(sumValue01, sumValue02);
return Map.of(
"accountId", accountId,
"sumValue01", sumValue01,
"sumValue02", sumValue02,
"sumValue03", sumValue03
);
})
));
for (Map.Entry<String, Map<String, Object>> entry : accountMap.entrySet()) {
String accountName = entry.getKey();
Map<String, Object> map = entry.getValue();
ChartDataItem item = new ChartDataItem();
item.setItemCode("CHART");
item.setAxisName(accountName);
item.setValue01(map.get("sumValue01").toString());
item.setValue02(map.get("sumValue02").toString());
item.setValue03(map.get("sumValue03").toString());
item.setBizKey(map.get("accountId").toString());
chartDataItems.add(item);
}
return chartDataItems;
}
/**
* 月份收支
*/
@RequestMapping(value = "getErpMonthChart")
@ResponseBody
public List<ChartDataItem> getErpMonthChart(ErpTransactionFlow erpTransactionFlow) {
List<ChartDataItem> chartDataItems = new ArrayList<>();
List<ErpTransactionFlow> flowList = erpTransactionFlowService.findList(erpTransactionFlow);
Map<String, Map<String, Object>> accountMap = flowList.stream()
.collect(Collectors.groupingBy(
ErpTransactionFlow::getMonthDate,
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(sumValue04, sumValue01);
return Map.of(
"sumValue01", sumValue01,
"sumValue02", sumValue02,
"sumValue03", sumValue03,
"sumValue04", sumValue04
);
})
));
for (Map.Entry<String, Map<String, Object>> entry : accountMap.entrySet()) {
String monthDate = entry.getKey();
Map<String, Object> map = entry.getValue();
ChartDataItem item = new ChartDataItem();
item.setAxisName(monthDate);
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;
}
/**
* 分类收支
*/
@RequestMapping(value = "getCategoryChart")
@ResponseBody
public List<ChartDataItem> getCategoryChart(ErpTransactionFlow erpTransactionFlow) {
List<ChartDataItem> chartDataItems = new ArrayList<>();
List<ErpTransactionFlow> flowList = erpTransactionFlowService.findList(erpTransactionFlow);
BigDecimal totalAmount = flowList.stream()
.map(ErpTransactionFlow::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Map<String, Map<String, Object>> accountMap = flowList.stream()
.collect(Collectors.groupingBy(
ErpTransactionFlow::getCategoryName,
Collectors.collectingAndThen(Collectors.toList(), list -> {
BigDecimal sumValue01 = list.stream()
.map(ErpTransactionFlow::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal sumValue02 = BigDecimalUtils.percent(sumValue01, totalAmount);
return Map.of(
"sumValue01", sumValue01,
"sumValue02", sumValue02
);
})
));
List<Map.Entry<String, Map<String, Object>>> sortedList = new ArrayList<>(accountMap.entrySet());
sortedList.sort((e1, e2) -> {
BigDecimal v1 = (BigDecimal) e1.getValue().get("sumValue02");
BigDecimal v2 = (BigDecimal) e2.getValue().get("sumValue02");
return v2.compareTo(v1);
});
long soring = 1;
for (Map.Entry<String, Map<String, Object>> entry : sortedList) {
String categoryName = entry.getKey();
Map<String, Object> map = entry.getValue();
ChartDataItem item = new ChartDataItem();
item.setSoring(soring++);
item.setAxisName(categoryName);
item.setValue01(map.get("sumValue01").toString());
item.setValue02(map.get("sumValue02").toString());
chartDataItems.add(item);
}
return chartDataItems;
}
}

View File

@@ -42,8 +42,8 @@ import java.io.Serial;
@Column(name = "remark", attrName = "remark", label = "交易备注", isQuery = false), @Column(name = "remark", attrName = "remark", label = "交易备注", isQuery = false),
@Column(name = "update_time", attrName = "updateTime", label = "更新时间", isQuery = false, isUpdateForce = true), @Column(name = "update_time", attrName = "updateTime", label = "更新时间", isQuery = false, isUpdateForce = true),
@Column(name = "business_id", attrName = "businessId", label = "业务标识"), @Column(name = "business_id", attrName = "businessId", label = "业务标识"),
@Column(name = "year_date", attrName = "yearDate", label = "业务标识"), @Column(name = "year_date", attrName = "yearDate", label = "年份"),
@Column(name = "month_date", attrName = "monthDate", label = "业务标识"), @Column(name = "month_date", attrName = "monthDate", label = "月份"),
}, joinTable = { }, joinTable = {
@JoinTable(type = Type.LEFT_JOIN, entity = ErpAccount.class, alias = "b", @JoinTable(type = Type.LEFT_JOIN, entity = ErpAccount.class, alias = "b",
on = "a.account_id = b.account_id", attrName = "this", on = "a.account_id = b.account_id", attrName = "this",

View File

@@ -48,6 +48,22 @@ public class BigDecimalUtils {
return num1.divide(num2, SCALE, ROUND_MODE); return num1.divide(num2, SCALE, ROUND_MODE);
} }
/**
* 百分比
*/
public static BigDecimal percent(BigDecimal num1, BigDecimal num2) {
num1 = defaultIfNull(num1);
num2 = defaultIfNull(num2);
if (num2.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
// 先除 → 再 ×100 → 保留2位
return num1.divide(num2, 4, ROUND_MODE)
.multiply(new BigDecimal("100"))
.setScale(SCALE, ROUND_MODE);
}
private static BigDecimal defaultIfNull(BigDecimal num) { private static BigDecimal defaultIfNull(BigDecimal num) {
return num == null ? BigDecimal.ZERO : num; return num == null ? BigDecimal.ZERO : num;
} }

View File

@@ -73,8 +73,9 @@ const switchTabByRoute = (item) => {
router.push({ router.push({
path: item.path, path: item.path,
query: { query: {
year: queryDate.value, ...route.query,
...route.query yearDate: queryDate.value,
} }
}).catch(err => { }).catch(err => {
ElMessage.error('页面切换失败,请重试'); ElMessage.error('页面切换失败,请重试');
@@ -90,7 +91,7 @@ const handleQuery = () => {
path: route.path, path: route.path,
query: { query: {
...route.query, ...route.query,
year: queryDate.value yearDate: queryDate.value,
} }
}); });
}; };
@@ -112,8 +113,8 @@ async function getList() {
onMounted(() => { onMounted(() => {
getList(); getList();
router.afterEach((to) => { router.afterEach((to) => {
if (to.query.year) { if (to.query.yearDate) {
queryDate.value = to.query.year; queryDate.value = to.query.yearDate;
} }
}); });
}); });

View File

@@ -7,46 +7,54 @@
</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, ErpAccountChart } 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();
const parseAmount = (value?: string | number) => {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? parsed : 0;
};
async function getList() { async function getList() {
try { try {
const params = { const params = {
...props.formParams, ...props.formParams,
itemCode: 'ERP_ACCOUNT_Y001', };
} const res = await ErpAccountChart(params);
const res = await getItemInfoList(params) vList.value = res || [];
vList.value = res || []
} catch (error) { } catch (error) {
console.error(error) console.error(error);
vList.value = [] vList.value = [];
} }
} }
function initChart() { function initChart() {
const el = chartRef.value const el = chartRef.value;
if (!el) return if (!el) return;
if (!chartInstance) { if (!chartInstance) {
chartInstance = echarts.init(el) chartInstance = echarts.init(el);
} }
const baseOption = { const baseOption: echarts.EChartsOption & {
tooltip: echarts.TooltipComponentOption;
xAxis: Array<echarts.XAXisComponentOption & { data: string[] }>;
series: echarts.SeriesOption[];
} = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross' }, axisPointer: { type: 'cross' },
@@ -55,20 +63,20 @@ function initChart() {
borderWidth: 1, borderWidth: 1,
textStyle: { color: '#0a3b70' }, textStyle: { color: '#0a3b70' },
padding: [8, 12], padding: [8, 12],
borderRadius: 6 borderRadius: 6,
}, },
legend: { legend: {
top: '10', top: '10',
left: 'center', left: 'center',
textStyle: { fontSize: 12, color: '#e0e6ff' }, textStyle: { fontSize: 12, color: '#e0e6ff' },
data: ['收入', '支出', '净利润'] data: ['收入', '支出', '净利润'],
}, },
grid: { grid: {
left: '5%', left: '5%',
right: '5%', right: '5%',
bottom: '10%', bottom: '10%',
top: '15%', top: '15%',
containLabel: true containLabel: true,
}, },
xAxis: [ xAxis: [
{ {
@@ -77,11 +85,11 @@ function initChart() {
axisLabel: { axisLabel: {
fontSize: 11, fontSize: 11,
interval: 0, interval: 0,
color: '#b4c7e7' color: '#b4c7e7',
}, },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
boundaryGap: true boundaryGap: true,
} },
], ],
yAxis: [ yAxis: [
{ {
@@ -90,7 +98,7 @@ function initChart() {
nameTextStyle: { fontSize: 12, color: '#b4c7e7' }, nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value}', color: '#b4c7e7' }, axisLabel: { formatter: '{value}', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } } splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } },
}, },
{ {
type: 'value', type: 'value',
@@ -101,8 +109,8 @@ function initChart() {
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } }, splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } },
min: 'dataMin', min: 'dataMin',
max: 'dataMax', max: 'dataMax',
scale: true scale: true,
} },
], ],
series: [ series: [
{ {
@@ -114,17 +122,17 @@ function initChart() {
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#85E868' }, { offset: 0, color: '#85E868' },
{ offset: 1, color: '#67C23A' } { offset: 1, color: '#67C23A' },
]), ]),
borderRadius: [8, 8, 0, 0] borderRadius: [8, 8, 0, 0],
}, },
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
fontSize: 10, fontSize: 10,
color: '#fff', color: '#fff',
formatter: '{c}' formatter: '{c}',
} },
}, },
{ {
name: '支出', name: '支出',
@@ -135,17 +143,17 @@ function initChart() {
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#FF8A8A' }, { offset: 0, color: '#FF8A8A' },
{ offset: 1, color: '#F56C6C' } { offset: 1, color: '#F56C6C' },
]), ]),
borderRadius: [8, 8, 0, 0] borderRadius: [8, 8, 0, 0],
}, },
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
fontSize: 10, fontSize: 10,
color: '#fff', color: '#fff',
formatter: '{c}' formatter: '{c}',
} },
}, },
{ {
name: '净利润', name: '净利润',
@@ -158,45 +166,41 @@ function initChart() {
symbolSize: 5, symbolSize: 5,
label: { label: {
show: true, show: true,
position: 'outside', position: 'top',
fontSize: 10, fontSize: 10,
color: '#409EFF', color: '#409EFF',
formatter: '{c}', formatter: '{c}',
offset: [0, -5] offset: [0, -5],
}, },
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, { offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.0)' } { offset: 1, color: 'rgba(64, 158, 255, 0.0)' },
]) ]),
}, },
emphasis: { emphasis: {
lineStyle: { width: 2 }, focus: 'series',
symbolSize: 7 },
} },
} ],
] };
}
if (vList.value.length > 0) { if (vList.value?.length > 0) {
const xData = vList.value.map(item => { const xData = vList.value.map((item) => item.axisName || '');
const xaxis = item.xaxis || '' const index01Yuan = vList.value.map((item) => parseAmount(item.value01));
return xaxis; const index02Yuan = vList.value.map((item) => parseAmount(item.value02));
}) const index03Yuan = vList.value.map((item) => parseAmount(item.value03));
const index01Yuan = vList.value.map(item => item.index01 || 0)
const index02Yuan = vList.value.map(item => item.index02 || 0)
const index03Yuan = vList.value.map(item => item.index03 || 0)
const index01Wan = index01Yuan.map(val => (val / 10000).toFixed(2)) const index01Wan = index01Yuan.map((val) => Number((val / 10000).toFixed(2)));
const index02Wan = index02Yuan.map(val => (val / 10000).toFixed(2)) const index02Wan = index02Yuan.map((val) => Number((val / 10000).toFixed(2)));
const index03Wan = index03Yuan.map(val => (val / 10000).toFixed(2)) const index03Wan = index03Yuan.map((val) => Number((val / 10000).toFixed(2)));
baseOption.tooltip.formatter = function (params) { baseOption.tooltip.formatter = function (params) {
const title = params[0].axisValue const title = params[0].axisValue;
const idx = params[0].dataIndex const idx = params[0].dataIndex;
const incomeVal = index01Yuan[idx] const incomeVal = index01Yuan[idx];
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">
@@ -211,42 +215,46 @@ function initChart() {
<td style="border:1px solid #409EFF; padding:4px">${netProfitVal}</td> <td style="border:1px solid #409EFF; padding:4px">${netProfitVal}</td>
</tr> </tr>
</table> </table>
` `;
};
baseOption.xAxis[0].data = xData;
baseOption.series[0].data = index01Wan;
baseOption.series[1].data = index02Wan;
baseOption.series[2].data = index03Wan;
} }
baseOption.xAxis[0].data = xData chartInstance.setOption(baseOption, true);
baseOption.series[0].data = index01Wan
baseOption.series[1].data = index02Wan
baseOption.series[2].data = index03Wan
}
chartInstance.setOption(baseOption, true)
} }
watch( watch(
() => props.formParams, () => props.formParams,
() => { () => {
getList() getList();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
) );
watch(vList, () => { watch(
nextTick(() => initChart()) vList,
}, { deep: true }) () => {
nextTick(() => initChart());
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
initChart() initChart();
window.addEventListener('resize', resizeHandler) window.addEventListener('resize', resizeHandler);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler);
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped> <style scoped>
@@ -256,7 +264,7 @@ onUnmounted(() => {
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%;
} }
@@ -268,14 +276,14 @@ onUnmounted(() => {
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/03.png") no-repeat; background: rgba(0, 0, 0, 0.1) url('@jeesite/assets/chart/title/03.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;
} }

View File

@@ -7,62 +7,81 @@
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from 'vue' import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts' import * as echarts from 'echarts';
// import { getItemInfoList } from '@/api/bizApi' import { ChartDataItem, CategoryChart } 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();
const parseAmount = (value?: string | number) => {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? parsed : 0;
};
async function getList() { async function getList() {
try { try {
const params = { const params = {
...props.formParams, ...props.formParams,
itemCode: 'ERP_CATEGORY_Y001', flowType: '1',
} };
const res = await getItemInfoList(params) const res = await CategoryChart(params);
vList.value = res || [] vList.value = res || [];
} catch (error) { } catch (error) {
console.error('获取支出数据失败:', error) console.error('获取支出数据失败:', error);
vList.value = [] vList.value = [];
} }
} }
const initPieChart = () => { const initPieChart = () => {
const el = chartRef.value const el = chartRef.value;
if (!el) return if (!el) return;
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
} }
chartInstance = echarts.init(el) chartInstance = echarts.init(el);
const pieData = vList.value.map(item => { const pieData = vList.value
const originalValue = item.index01 || 0 .map((item) => {
const wanValue = (originalValue / 10000).toFixed(2) const originalValue = parseAmount(item.value01);
const wanValue = (originalValue / 10000).toFixed(2);
return { return {
name: item.xaxis || '未知分类', name: item.axisName || '未知分类',
value: Number(wanValue), value: Number(wanValue),
originalValue: originalValue originalValue: originalValue,
} };
}).filter(item => item.value > 0) })
.filter((item) => item.value > 0);
const colorList = [ const colorList = [
'#409EFF', '#36CFc9', '#67C23A', '#E6A23C', '#F56C6C', '#409EFF',
'#909399', '#722ED1', '#EB2F96', '#1890FF', '#52C41A', '#36CFc9',
'#FAAD14', '#F5222D', '#8C8C8C', '#A062D4', '#F7BA1E' '#67C23A',
] '#E6A23C',
'#F56C6C',
'#909399',
'#722ED1',
'#EB2F96',
'#1890FF',
'#52C41A',
'#FAAD14',
'#F5222D',
'#8C8C8C',
'#A062D4',
'#F7BA1E',
];
const option = { const option = {
tooltip: { tooltip: {
@@ -74,8 +93,8 @@ const initPieChart = () => {
padding: [10, 15], padding: [10, 15],
borderRadius: 6, borderRadius: 6,
formatter: function (params) { formatter: function (params) {
return `分类:${params.name}<br/>支出:${params.data.originalValue}元<br/>占比:${params.percent.toFixed(1)}%` return `分类:${params.name}<br/>支出:${params.data.originalValue}元<br/>占比:${params.percent.toFixed(1)}%`;
} },
}, },
legend: { legend: {
orient: 'horizontal', orient: 'horizontal',
@@ -89,7 +108,7 @@ const initPieChart = () => {
pageTextStyle: { color: '#e0e6ff', fontSize: 10 }, pageTextStyle: { color: '#e0e6ff', fontSize: 10 },
pageButtonItemGap: 6, pageButtonItemGap: 6,
pageButtonGap: 10, pageButtonGap: 10,
type: 'scroll' type: 'scroll',
}, },
series: [ series: [
{ {
@@ -101,7 +120,7 @@ const initPieChart = () => {
itemStyle: { itemStyle: {
borderRadius: 4, borderRadius: 4,
borderColor: 'rgba(15, 52, 96, 0.9)', borderColor: 'rgba(15, 52, 96, 0.9)',
borderWidth: 1 borderWidth: 1,
}, },
label: { label: {
show: true, show: true,
@@ -111,7 +130,7 @@ const initPieChart = () => {
formatter: '{b} {c}万元 ({d}%)', formatter: '{b} {c}万元 ({d}%)',
overflow: 'truncate', overflow: 'truncate',
ellipsis: '...', ellipsis: '...',
distance: 8 distance: 8,
}, },
labelLine: { labelLine: {
show: true, show: true,
@@ -119,40 +138,44 @@ const initPieChart = () => {
length2: 8, length2: 8,
lineStyle: { color: '#e0e6ff', width: 1 }, lineStyle: { color: '#e0e6ff', width: 1 },
smooth: 0.2, smooth: 0.2,
minTurnAngle: 45 minTurnAngle: 45,
}, },
data: pieData, data: pieData,
color: colorList color: colorList,
} },
] ],
} };
chartInstance.setOption(option) chartInstance.setOption(option);
} };
watch( watch(
() => props.formParams, () => props.formParams,
() => { () => {
getList() getList();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
) );
watch(vList, () => { watch(
initPieChart() vList,
}, { deep: true }) () => {
initPieChart();
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeHandler) window.addEventListener('resize', resizeHandler);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler);
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped> <style scoped>
@@ -162,7 +185,7 @@ onUnmounted(() => {
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%;
} }
@@ -174,14 +197,14 @@ onUnmounted(() => {
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;
} }
@@ -195,7 +218,7 @@ onUnmounted(() => {
:deep(.echarts-tooltip) { :deep(.echarts-tooltip) {
background-color: rgba(145, 200, 255, 0.9) !important; background-color: rgba(145, 200, 255, 0.9) !important;
border-color: #409EFF !important; border-color: #409eff !important;
color: #0a3b70 !important; color: #0a3b70 !important;
border-radius: 6px !important; border-radius: 6px !important;
} }
@@ -211,7 +234,7 @@ onUnmounted(() => {
border-color: #1a508b !important; border-color: #1a508b !important;
} }
:deep(.echarts-legend-scroll-button-icon) { :deep(.echarts-legend-scroll-button-icon) {
color: #409EFF !important; color: #409eff !important;
} }
:deep(.ec-label) { :deep(.ec-label) {

View File

@@ -7,62 +7,81 @@
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from 'vue' import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts' import * as echarts from 'echarts';
// import { getItemInfoList } from '@/api/bizApi' import { ChartDataItem, CategoryChart } 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();
const parseAmount = (value?: string | number) => {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? parsed : 0;
};
async function getList() { async function getList() {
try { try {
const params = { const params = {
...props.formParams, ...props.formParams,
itemCode: 'ERP_CATEGORY_Y002', flowType: '2',
} };
const res = await getItemInfoList(params) const res = await CategoryChart(params);
vList.value = res || [] vList.value = res || [];
} catch (error) { } catch (error) {
console.error('获取收入数据失败:', error) console.error('获取收入数据失败:', error);
vList.value = [] vList.value = [];
} }
} }
const initPieChart = () => { const initPieChart = () => {
const el = chartRef.value const el = chartRef.value;
if (!el) return if (!el) return;
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
} }
chartInstance = echarts.init(el) chartInstance = echarts.init(el);
const pieData = vList.value.map(item => { const pieData = vList.value
const originalValue = item.index01 || 0 .map((item) => {
const wanValue = (originalValue / 10000).toFixed(2) const originalValue = parseAmount(item.value01);
const wanValue = (originalValue / 10000).toFixed(2);
return { return {
name: item.xaxis || '未知分类', name: item.axisName || '未知分类',
value: Number(wanValue), value: Number(wanValue),
originalValue: originalValue originalValue: originalValue,
} };
}).filter(item => item.value > 0) })
.filter((item) => item.value > 0);
const colorList = [ const colorList = [
'#409EFF', '#36CFc9', '#67C23A', '#E6A23C', '#F56C6C', '#409EFF',
'#909399', '#722ED1', '#EB2F96', '#1890FF', '#52C41A', '#36CFc9',
'#FAAD14', '#F5222D', '#8C8C8C', '#A062D4', '#F7BA1E' '#67C23A',
] '#E6A23C',
'#F56C6C',
'#909399',
'#722ED1',
'#EB2F96',
'#1890FF',
'#52C41A',
'#FAAD14',
'#F5222D',
'#8C8C8C',
'#A062D4',
'#F7BA1E',
];
const option = { const option = {
tooltip: { tooltip: {
@@ -74,8 +93,8 @@ const initPieChart = () => {
padding: [10, 15], padding: [10, 15],
borderRadius: 6, borderRadius: 6,
formatter: function (params) { formatter: function (params) {
return `分类:${params.name}<br/>收入:${params.data.originalValue}元<br/>占比:${params.percent.toFixed(1)}%` return `分类:${params.name}<br/>收入:${params.data.originalValue}元<br/>占比:${params.percent.toFixed(1)}%`;
} },
}, },
legend: { legend: {
orient: 'horizontal', orient: 'horizontal',
@@ -89,7 +108,7 @@ const initPieChart = () => {
pageTextStyle: { color: '#e0e6ff', fontSize: 10 }, pageTextStyle: { color: '#e0e6ff', fontSize: 10 },
pageButtonItemGap: 6, pageButtonItemGap: 6,
pageButtonGap: 10, pageButtonGap: 10,
type: 'scroll' type: 'scroll',
}, },
series: [ series: [
{ {
@@ -101,7 +120,7 @@ const initPieChart = () => {
itemStyle: { itemStyle: {
borderRadius: 4, borderRadius: 4,
borderColor: 'rgba(15, 52, 96, 0.9)', borderColor: 'rgba(15, 52, 96, 0.9)',
borderWidth: 1 borderWidth: 1,
}, },
label: { label: {
show: true, show: true,
@@ -111,7 +130,7 @@ const initPieChart = () => {
formatter: '{b} {c}万元 ({d}%)', formatter: '{b} {c}万元 ({d}%)',
overflow: 'truncate', overflow: 'truncate',
ellipsis: '...', ellipsis: '...',
distance: 8 distance: 8,
}, },
labelLine: { labelLine: {
show: true, show: true,
@@ -119,40 +138,44 @@ const initPieChart = () => {
length2: 8, length2: 8,
lineStyle: { color: '#e0e6ff', width: 1 }, lineStyle: { color: '#e0e6ff', width: 1 },
smooth: 0.2, smooth: 0.2,
minTurnAngle: 45 minTurnAngle: 45,
}, },
data: pieData, data: pieData,
color: colorList color: colorList,
} },
] ],
} };
chartInstance.setOption(option) chartInstance.setOption(option);
} };
watch( watch(
() => props.formParams, () => props.formParams,
() => { () => {
getList() getList();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
) );
watch(vList, () => { watch(
initPieChart() vList,
}, { deep: true }) () => {
initPieChart();
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
window.addEventListener('resize', resizeHandler) window.addEventListener('resize', resizeHandler);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler);
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped> <style scoped>
@@ -162,7 +185,7 @@ onUnmounted(() => {
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%;
} }
@@ -174,14 +197,14 @@ onUnmounted(() => {
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;
} }
@@ -195,7 +218,7 @@ onUnmounted(() => {
:deep(.echarts-tooltip) { :deep(.echarts-tooltip) {
background-color: rgba(145, 200, 255, 0.9) !important; background-color: rgba(145, 200, 255, 0.9) !important;
border-color: #409EFF !important; border-color: #409eff !important;
color: #0a3b70 !important; color: #0a3b70 !important;
border-radius: 6px !important; border-radius: 6px !important;
} }
@@ -211,7 +234,7 @@ onUnmounted(() => {
border-color: #1a508b !important; border-color: #1a508b !important;
} }
:deep(.echarts-legend-scroll-button-icon) { :deep(.echarts-legend-scroll-button-icon) {
color: #409EFF !important; color: #409eff !important;
} }
:deep(.ec-label) { :deep(.ec-label) {

View File

@@ -7,46 +7,59 @@
</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, ErpMonthChart } 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();
const parseAmount = (value?: string | number) => {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? parsed : 0;
};
const parseRate = (value?: string | number) => {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? Number(parsed.toFixed(2)) : 0;
};
async function getList() { async function getList() {
try { try {
const params = { const params = {
...props.formParams, ...props.formParams,
itemCode: 'ERP_YEARPSAV_M001', };
} const res = await ErpMonthChart(params);
const res = await getItemInfoList(params) vList.value = res || [];
vList.value = res || []
} catch (error) { } catch (error) {
console.error(error) console.error(error);
vList.value = [] vList.value = [];
} }
} }
function initChart() { function initChart() {
const el = chartRef.value const el = chartRef.value;
if (!el) return if (!el) return;
if (!chartInstance) { if (!chartInstance) {
chartInstance = echarts.init(el) chartInstance = echarts.init(el);
} }
const baseOption = { const baseOption: echarts.EChartsOption & {
tooltip: echarts.TooltipComponentOption;
xAxis: Array<echarts.XAXisComponentOption & { data: string[] }>;
series: echarts.SeriesOption[];
} = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'cross' }, axisPointer: { type: 'cross' },
@@ -55,20 +68,20 @@ function initChart() {
borderWidth: 1, borderWidth: 1,
textStyle: { color: '#0a3b70' }, textStyle: { color: '#0a3b70' },
padding: [8, 12], padding: [8, 12],
borderRadius: 6 borderRadius: 6,
}, },
legend: { legend: {
top: '10', top: '10',
left: 'center', left: 'center',
textStyle: { fontSize: 12, color: '#e0e6ff' }, textStyle: { fontSize: 12, color: '#e0e6ff' },
data: ['收入', '支出', '利润率', '净利润'] data: ['收入', '支出', '利润率', '净利润'],
}, },
grid: { grid: {
left: '5%', left: '5%',
right: '5%', right: '5%',
bottom: '10%', bottom: '10%',
top: '15%', top: '15%',
containLabel: true containLabel: true,
}, },
xAxis: [ xAxis: [
{ {
@@ -77,11 +90,11 @@ function initChart() {
axisLabel: { axisLabel: {
fontSize: 11, fontSize: 11,
interval: 0, interval: 0,
color: '#b4c7e7' color: '#b4c7e7',
}, },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
boundaryGap: true boundaryGap: true,
} },
], ],
yAxis: [ yAxis: [
{ {
@@ -90,7 +103,7 @@ function initChart() {
nameTextStyle: { fontSize: 12, color: '#b4c7e7' }, nameTextStyle: { fontSize: 12, color: '#b4c7e7' },
axisLabel: { formatter: '{value}', color: '#b4c7e7' }, axisLabel: { formatter: '{value}', color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } } splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } },
}, },
{ {
type: 'value', type: 'value',
@@ -101,8 +114,8 @@ function initChart() {
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } }, splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.2)' } },
min: 'dataMin', min: 'dataMin',
max: 'dataMax', max: 'dataMax',
scale: true scale: true,
} },
], ],
series: [ series: [
{ {
@@ -114,17 +127,17 @@ function initChart() {
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#85E868' }, { offset: 0, color: '#85E868' },
{ offset: 1, color: '#67C23A' } { offset: 1, color: '#67C23A' },
]), ]),
borderRadius: [8, 8, 0, 0] borderRadius: [8, 8, 0, 0],
}, },
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
fontSize: 10, fontSize: 10,
color: '#fff', color: '#fff',
formatter: '{c}' formatter: '{c}',
} },
}, },
{ {
name: '支出', name: '支出',
@@ -135,17 +148,17 @@ function initChart() {
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#FF8A8A' }, { offset: 0, color: '#FF8A8A' },
{ offset: 1, color: '#F56C6C' } { offset: 1, color: '#F56C6C' },
]), ]),
borderRadius: [8, 8, 0, 0] borderRadius: [8, 8, 0, 0],
}, },
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
fontSize: 10, fontSize: 10,
color: '#fff', color: '#fff',
formatter: '{c}' formatter: '{c}',
} },
}, },
{ {
name: '利润率', name: '利润率',
@@ -158,22 +171,21 @@ function initChart() {
symbolSize: 5, symbolSize: 5,
label: { label: {
show: true, show: true,
position: 'outside', position: 'top',
fontSize: 10, fontSize: 10,
color: '#409EFF', color: '#409EFF',
formatter: '{c}', formatter: '{c}',
offset: [0, -5] offset: [0, -5],
}, },
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, { offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.0)' } { offset: 1, color: 'rgba(64, 158, 255, 0.0)' },
]) ]),
}, },
emphasis: { emphasis: {
lineStyle: { width: 2 }, focus: 'series',
symbolSize: 7 },
}
}, },
{ {
name: '净利润', name: '净利润',
@@ -188,43 +200,42 @@ function initChart() {
show: true, show: true,
fontSize: 10, fontSize: 10,
color: '#FF9D28', color: '#FF9D28',
formatter: '{c}' formatter: '{c}',
}, },
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 157, 40, 0.3)' }, { offset: 0, color: 'rgba(255, 157, 40, 0.3)' },
{ offset: 1, color: 'rgba(255, 157, 40, 0)' } { offset: 1, color: 'rgba(255, 157, 40, 0)' },
]) ]),
}, },
emphasis: { emphasis: {
lineStyle: { width: 2 }, focus: 'series',
symbolSize: 7 },
} },
} ],
] };
}
if (vList.value.length > 0) { if (vList.value?.length > 0) {
const xData = vList.value.map(item => { const xData = vList.value.map((item) => {
const xaxis = item.xaxis || '' const axisName = item.axisName || '';
return xaxis.includes('月') ? xaxis : `${xaxis}`; return axisName.includes('月') ? axisName : `${axisName}`;
}) });
const index01Yuan = vList.value.map(item => item.index01 || 0) const index01Yuan = vList.value.map((item) => parseAmount(item.value01));
const index02Yuan = vList.value.map(item => item.index02 || 0) const index02Yuan = vList.value.map((item) => parseAmount(item.value02));
const index03 = vList.value.map(item => item.index03 || 0) const index03 = vList.value.map((item) => parseRate(item.value03));
const index04Yuan = vList.value.map(item => item.index04 || 0) const index04Yuan = vList.value.map((item) => parseAmount(item.value04));
const index01Wan = index01Yuan.map(val => (val / 10000).toFixed(2)) const index01Wan = index01Yuan.map((val) => Number((val / 10000).toFixed(2)));
const index02Wan = index02Yuan.map(val => (val / 10000).toFixed(2)) const index02Wan = index02Yuan.map((val) => Number((val / 10000).toFixed(2)));
const index04Wan = index04Yuan.map(val => (val / 10000).toFixed(2)) const index04Wan = index04Yuan.map((val) => Number((val / 10000).toFixed(2)));
baseOption.tooltip.formatter = function (params) { baseOption.tooltip.formatter = function (params) {
const title = params[0].axisValue const title = params[0].axisValue;
const idx = params[0].dataIndex const idx = params[0].dataIndex;
const incomeVal = index01Yuan[idx] const incomeVal = index01Yuan[idx];
const expenseVal = index02Yuan[idx] const expenseVal = index02Yuan[idx];
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">
@@ -241,43 +252,47 @@ function initChart() {
<td style="border:1px solid #409EFF; padding:4px">${profitVal}</td> <td style="border:1px solid #409EFF; padding:4px">${profitVal}</td>
</tr> </tr>
</table> </table>
` `;
};
baseOption.xAxis[0].data = xData;
baseOption.series[0].data = index01Wan;
baseOption.series[1].data = index02Wan;
baseOption.series[2].data = index03;
baseOption.series[3].data = index04Wan;
} }
baseOption.xAxis[0].data = xData chartInstance.setOption(baseOption, true);
baseOption.series[0].data = index01Wan
baseOption.series[1].data = index02Wan
baseOption.series[2].data = index03
baseOption.series[3].data = index04Wan
}
chartInstance.setOption(baseOption, true)
} }
watch( watch(
() => props.formParams, () => props.formParams,
() => { () => {
getList() getList();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
) );
watch(vList, () => { watch(
nextTick(() => initChart()) vList,
}, { deep: true }) () => {
nextTick(() => initChart());
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
initChart() initChart();
window.addEventListener('resize', resizeHandler) window.addEventListener('resize', resizeHandler);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler);
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped> <style scoped>
@@ -287,7 +302,7 @@ onUnmounted(() => {
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%;
} }
@@ -299,14 +314,14 @@ onUnmounted(() => {
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/03.png") no-repeat; background: rgba(0, 0, 0, 0.1) url('@jeesite/assets/chart/title/03.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;
} }

View File

@@ -7,55 +7,53 @@
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from 'vue' import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts' import * as echarts from 'echarts';
// import { getItemInfoList } from '@/api/bizApi' import { ChartDataItem, CategoryChart } 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_YEARRANK_Y001', flowType: '1',
} };
const res = await getItemInfoList(params) const res = await CategoryChart(params);
vList.value = res || [] vList.value = res || [];
} catch (error) { } catch (error) {
console.error(error) console.error(error);
vList.value = [] vList.value = [];
} }
} }
const initRankChart = () => { const initRankChart = () => {
const el = chartRef.value const el = chartRef.value;
if (!el) return if (!el) return;
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
} }
chartInstance = echarts.init(el) chartInstance = echarts.init(el);
const sortedList = [...vList.value] const sortedList = [...vList.value].sort((a, b) => Number(b.value01 || 0) - Number(a.value01 || 0)).slice(0, 10);
.sort((a, b) => Number(b.index01 || 0) - Number(a.index01 || 0))
.slice(0, 10)
const yData = sortedList.map((item, index) => `${index + 1}. ${item.xaxis || '未知'}`) const yData = sortedList.map((item, index) => `${index + 1}. ${item.axisName || '未知'}`);
const valueData = sortedList.map(item => Number((Number(item.index01 || 0) / 10000).toFixed(2)) || 0) const valueData = sortedList.map((item) => Number((Number(item.value01 || 0) / 10000).toFixed(2)) || 0);
const percentData = sortedList.map(item => Number(Number(item.index02 || 0).toFixed(2)) || 0) const percentData = sortedList.map((item) => Number(Number(item.value02 || 0).toFixed(2)) || 0);
const originalData = sortedList.map(item => Number(item.index01 || 0)) const originalData = sortedList.map((item) => Number(item.value01 || 0));
const option = { const option = {
tooltip: { tooltip: {
@@ -67,11 +65,11 @@ const initRankChart = () => {
textStyle: { color: '#0a3b70' }, textStyle: { color: '#0a3b70' },
padding: [8, 12], padding: [8, 12],
borderRadius: 6, borderRadius: 6,
formatter: params => { formatter: (params) => {
const idx = params[0].dataIndex const idx = params[0].dataIndex;
const name = params[0].name const name = params[0].name;
const amount = originalData[idx] const amount = originalData[idx];
const rate = percentData[idx] const rate = percentData[idx];
return ` return `
<div style="text-align:center; font-weight:bold; margin-bottom:6px">${name}</div> <div style="text-align:center; font-weight:bold; margin-bottom:6px">${name}</div>
<table style="width:100%; border-collapse:collapse; text-align:center"> <table style="width:100%; border-collapse:collapse; text-align:center">
@@ -84,26 +82,26 @@ const initRankChart = () => {
<td style="border:1px solid #409EFF; padding:4px">${rate}</td> <td style="border:1px solid #409EFF; padding:4px">${rate}</td>
</tr> </tr>
</table> </table>
` `;
} },
}, },
legend: { legend: {
show: false show: false,
}, },
grid: { grid: {
left: '5%', left: '5%',
right: '15%', right: '15%',
bottom: '10%', bottom: '10%',
top: '15%', top: '15%',
containLabel: true containLabel: true,
}, },
xAxis: [ xAxis: [
{ {
type: 'value', type: 'value',
axisLabel: { fontSize: 11, color: '#b4c7e7' }, axisLabel: { fontSize: 11, color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } } splitLine: { lineStyle: { color: 'rgba(26, 80, 139, 0.3)' } },
} },
], ],
yAxis: [ yAxis: [
{ {
@@ -111,8 +109,8 @@ const initRankChart = () => {
data: yData, data: yData,
axisLabel: { fontSize: 11, color: '#b4c7e7' }, axisLabel: { fontSize: 11, color: '#b4c7e7' },
axisLine: { lineStyle: { color: '#1a508b' } }, axisLine: { lineStyle: { color: '#1a508b' } },
inverse: true inverse: true,
} },
], ],
series: [ series: [
{ {
@@ -124,43 +122,51 @@ const initRankChart = () => {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#409EFF' }, { offset: 0, color: '#409EFF' },
{ offset: 0, color: '#409EFF' }, { offset: 0, color: '#409EFF' },
{ offset: 1, color: '#1890FF' } { offset: 1, color: '#1890FF' },
]) ]),
}, },
label: { label: {
show: true, show: true,
position: 'right', position: 'right',
fontSize: 10, fontSize: 10,
color: '#e0e6ff', color: '#e0e6ff',
formatter: params => `${params.value} 万元 (${percentData[params.dataIndex]}%)` formatter: (params) => `${params.value} 万元 (${percentData[params.dataIndex]}%)`,
} },
} },
] ],
} };
chartInstance.setOption(option) chartInstance.setOption(option);
} };
watch(() => props.formParams, () => { watch(
getList() () => props.formParams,
}, { deep: true, immediate: true }) () => {
getList();
},
{ deep: true, immediate: true },
);
watch(vList, () => { watch(
initRankChart() vList,
}, { deep: true }) () => {
initRankChart();
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
initRankChart() initRankChart();
window.addEventListener('resize', resizeHandler) window.addEventListener('resize', resizeHandler);
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resizeHandler) window.removeEventListener('resize', resizeHandler);
if (chartInstance) { if (chartInstance) {
chartInstance.dispose() chartInstance.dispose();
chartInstance = null chartInstance = null;
} }
}) });
</script> </script>
<style scoped> <style scoped>
@@ -170,7 +176,7 @@ onUnmounted(() => {
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%;
} }
@@ -182,14 +188,14 @@ onUnmounted(() => {
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;
} }
@@ -201,7 +207,7 @@ onUnmounted(() => {
:deep(.echarts-tooltip) { :deep(.echarts-tooltip) {
background-color: rgba(145, 200, 255, 0.9) !important; background-color: rgba(145, 200, 255, 0.9) !important;
border-color: #409EFF !important; border-color: #409eff !important;
color: #0a3b70 !important; color: #0a3b70 !important;
border-radius: 6px !important; border-radius: 6px !important;
} }

View File

@@ -93,7 +93,7 @@ const route = useRoute();
// 1. 完善类型定义,避免 any 类型 // 1. 完善类型定义,避免 any 类型
const FormValues = ref({ const FormValues = ref({
reqParam: route.query.year || '' yearDate: route.query.yearDate || ''
}); });
// 2. 定义组件映射的类型 // 2. 定义组件映射的类型
@@ -143,9 +143,9 @@ async function getChartList() {
// 7. 监听路由参数变化 // 7. 监听路由参数变化
watch( watch(
() => route.query.year, () => route.query.yearDate,
(newVal) => { (newVal) => {
FormValues.value.reqParam = newVal || ''; FormValues.value.yearDate = newVal || '';
}, },
{ {
deep: true, deep: true,

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author gaoxq
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface ErpTransactionFlow extends BasicModel<ErpTransactionFlow> {
createTime?: string; // 记录时间
flowId?: string; // 流水
flowName: string; // 交易名称
flowType: string; // 交易类型
amount: number; // 交易金额
tradeTime: string; // 交易时间
accountId: string; // 交易账户
categoryId: string; // 交易分类
remark?: string; // 交易备注
updateTime?: string; // 更新时间
businessId: string; // 业务标识
yearDate: string; //年份
monthDate: string; //月份
}
export interface ChartDataItem extends BasicModel<ChartDataItem> {
itemCode: string; // 项目编号
soring: number; // 序号
axisName: string; // X轴名称
value01: string; // 指标01
value02: string; // 指标02
value03: string; // 指标03
value04: string; // 指标04
value05: string; // 指标05
value06: string; // 指标06
value07: string; // 指标07
value08: string; // 指标08
value09: string; // 指标09
value10: string; // 指标10
value11: string; // 指标11
value12: string; // 指标12
value13: string; // 指标13
value14: string; // 指标14
value15: string; // 指标15
bizKey: string; // 业务主键编号
indexSum: number; // 指标求和
indexAvg: number; // 指标平均
indexMax: number; // 指标最大
indexMin: number; // 指标最小
}
export const ErpMonthChart = (params?: ErpTransactionFlow | any) =>
defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getErpMonthChart', params });
export const CategoryChart = (params?: ErpTransactionFlow | any) =>
defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getCategoryChart', params });
export const ErpAccountChart = (params?: ErpTransactionFlow | any) =>
defHttp.get<ChartDataItem[]>({ url: adminPath + '/erp/screen/getErpAccountChart', params });