新增预警页面

This commit is contained in:
2025-12-10 00:08:30 +08:00
parent ec2622743a
commit 2de5b51e07
11 changed files with 638 additions and 116 deletions

View File

@@ -7,60 +7,5 @@ import java.util.Date;
public class DateUtils { public class DateUtils {
// 日期格式化器(线程安全,复用提升性能)
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; // yyyy-MM-dd
private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); // yyyy-MM
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public static String calculateStartCycleCode(String cycleType) {
// 默认使用当前日期计算,留参便于测试时指定日期
return calculateStartCycleCode(cycleType, LocalDate.now());
}
/**
* 重载方法支持指定基准日期计算起始cycleCode便于测试
*
* @param cycleType 周期类型D:日, M:月, Q:季度, Y:年)
* @param baseDate 基准日期(以此日期为起点计算时间范围)
* @return 起始cycleCode符合对应格式未识别类型返回null
*/
public static String calculateStartCycleCode(String cycleType, LocalDate baseDate) {
if (baseDate == null) {
throw new IllegalArgumentException("基准日期不能为null");
}
if (cycleType == null) {
return null;
}
return switch (cycleType) {
case "D" ->
// 日最近30天格式 yyyy-MM-dd
baseDate.minusDays(30).format(DAY_FORMATTER);
case "M" ->
// 月最近1年格式 yyyy-MM
baseDate.minusYears(1).format(MONTH_FORMATTER);
case "Q" ->
// 季度最近3年格式 yyyy-Qx
getQuarterCycleCode(baseDate.minusYears(3));
case "Y" ->
// 年最近6年格式 yyyy
String.valueOf(baseDate.minusYears(6).getYear());
default ->
// 未识别的周期类型返回null
null;
};
}
/**
* 计算指定日期对应的季度cycleCode格式yyyy-Qx
*
* @param date 日期
* @return 季度cycleCode如2023-Q3
*/
private static String getQuarterCycleCode(LocalDate date) {
int month = date.getMonthValue(); // 1-12月
int quarter = (month - 1) / 3 + 1;
return String.format("%d-Q%d", date.getYear(), quarter);
}
} }

View File

@@ -111,4 +111,56 @@ public class vDate {
result.append(seconds).append(""); result.append(seconds).append("");
return result.toString(); return result.toString();
} }
public static String calculateStartCycleCode(String cycleType) {
// 默认使用当前日期计算,留参便于测试时指定日期
return calculateStartCycleCode(cycleType, LocalDate.now());
}
/**
* 重载方法支持指定基准日期计算起始cycleCode便于测试
*
* @param cycleType 周期类型D:日, M:月, Q:季度, Y:年)
* @param baseDate 基准日期(以此日期为起点计算时间范围)
* @return 起始cycleCode符合对应格式未识别类型返回null
*/
public static String calculateStartCycleCode(String cycleType, LocalDate baseDate) {
if (baseDate == null) {
throw new IllegalArgumentException("基准日期不能为null");
}
if (cycleType == null) {
return null;
}
return switch (cycleType) {
case "D" ->
// 日最近30天格式 yyyy-MM-dd
baseDate.minusDays(30).format(DAY_FORMATTER);
case "M" ->
// 月最近1年格式 yyyy-MM
baseDate.minusYears(1).format(MONTH_FORMATTER);
case "Q" ->
// 季度最近3年格式 yyyy-Qx
getQuarterCycleCode(baseDate.minusYears(3));
case "Y" ->
// 年最近6年格式 yyyy
String.valueOf(baseDate.minusYears(6).getYear());
default ->
// 未识别的周期类型返回null
null;
};
}
/**
* 计算指定日期对应的季度cycleCode格式yyyy-Qx
*
* @param date 日期
* @return 季度cycleCode如2023-Q3
*/
private static String getQuarterCycleCode(LocalDate date) {
int month = date.getMonthValue(); // 1-12月
int quarter = (month - 1) / 3 + 1;
return String.format("%d-Q%d", date.getYear(), quarter);
}
} }

View File

@@ -0,0 +1,15 @@
package com.jeesite.modules.erp.dao;
import com.jeesite.common.dao.CrudDao;
import com.jeesite.common.mybatis.annotation.MyBatisDao;
import com.jeesite.modules.erp.entity.ErpSummaryAll;
/**
* 汇总信息DAO接口
* @author gaoxq
* @version 2025-12-09
*/
@MyBatisDao(dataSourceName="work")
public interface ErpSummaryAllDao extends CrudDao<ErpSummaryAll> {
}

View File

@@ -0,0 +1,83 @@
package com.jeesite.modules.erp.entity;
import java.io.Serializable;
import java.util.Date;
import com.jeesite.common.entity.DataEntity;
import com.jeesite.common.mybatis.annotation.Column;
import com.jeesite.common.mybatis.annotation.Table;
import com.jeesite.common.mybatis.mapper.query.QueryType;
import com.jeesite.common.utils.excel.annotation.ExcelField;
import com.jeesite.common.utils.excel.annotation.ExcelField.Align;
import com.jeesite.common.utils.excel.annotation.ExcelFields;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 汇总信息Entity
*
* @author gaoxq
* @version 2025-12-09
*/
@EqualsAndHashCode(callSuper = true)
@Table(name = "erp_summary_all", alias = "a", label = "汇总信息信息", columns = {
@Column(name = "create_time", attrName = "createTime", label = "记录时间", isQuery = false, isUpdateForce = true),
@Column(name = "id", attrName = "id", label = "主键ID", isPK = true),
@Column(name = "c_date", attrName = "cdate", label = "汇总日期"),
@Column(name = "c_type", attrName = "ctype", label = "交易类型", isQuery = false),
@Column(name = "this_value", attrName = "thisValue", label = "当期金额", isQuery = false, isUpdateForce = true),
@Column(name = "prev_value", attrName = "prevValue", label = "上期金额", isQuery = false, isUpdateForce = true),
@Column(name = "mom_rate", attrName = "momRate", label = "环比", isQuery = false, isUpdateForce = true),
@Column(name = "f_cycle", attrName = "fcycle", label = "周期类型"),
}, orderBy = "a.c_date"
)
@Data
public class ErpSummaryAll extends DataEntity<ErpSummaryAll> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Date createTime; // 记录时间
private String cdate; // 汇总日期
private String ctype; // 交易类型
private Double thisValue; // 当期金额
private Double prevValue; // 上期金额
private Double momRate; // 环比
private String fcycle; // 周期类型
@ExcelFields({
@ExcelField(title = "记录时间", attrName = "createTime", align = Align.CENTER, sort = 10, dataFormat = "yyyy-MM-dd hh:mm"),
@ExcelField(title = "主键ID", attrName = "id", align = Align.CENTER, sort = 20),
@ExcelField(title = "汇总日期", attrName = "cdate", align = Align.CENTER, sort = 30, dataFormat = "yyyy-MM-dd"),
@ExcelField(title = "交易类型", attrName = "ctype", dictType = "transaction_type", align = Align.CENTER, sort = 40),
@ExcelField(title = "当期金额", attrName = "thisValue", align = Align.CENTER, sort = 50),
@ExcelField(title = "上期金额", attrName = "prevValue", align = Align.CENTER, sort = 60),
@ExcelField(title = "环比", attrName = "momRate", align = Align.CENTER, sort = 70),
@ExcelField(title = "周期类型", attrName = "fcycle", dictType = "report_cycle", align = Align.CENTER, sort = 80),
})
public ErpSummaryAll() {
this(null);
}
public ErpSummaryAll(String id) {
super(id);
}
public String getCdate_gte() {
return sqlMap.getWhere().getValue("c_date", QueryType.GTE);
}
public void setCdate_gte(String cdate) {
sqlMap.getWhere().and("c_date", QueryType.GTE, cdate);
}
public String getCdate_lte() {
return sqlMap.getWhere().getValue("c_date", QueryType.LTE);
}
public void setCdate_lte(String cdate) {
sqlMap.getWhere().and("c_date", QueryType.LTE, cdate);
}
}

View File

@@ -0,0 +1,135 @@
package com.jeesite.modules.erp.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.jeesite.common.entity.Page;
import com.jeesite.common.service.CrudService;
import com.jeesite.modules.erp.entity.ErpSummaryAll;
import com.jeesite.modules.erp.dao.ErpSummaryAllDao;
import com.jeesite.common.service.ServiceException;
import com.jeesite.common.config.Global;
import com.jeesite.common.validator.ValidatorUtils;
import com.jeesite.common.utils.excel.ExcelImport;
import org.springframework.web.multipart.MultipartFile;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
/**
* 汇总信息Service
* @author gaoxq
* @version 2025-12-09
*/
@Service
public class ErpSummaryAllService extends CrudService<ErpSummaryAllDao, ErpSummaryAll> {
/**
* 获取单条数据
* @param erpSummaryAll 主键
*/
@Override
public ErpSummaryAll get(ErpSummaryAll erpSummaryAll) {
return super.get(erpSummaryAll);
}
/**
* 查询分页数据
* @param erpSummaryAll 查询条件
* @param erpSummaryAll page 分页对象
*/
@Override
public Page<ErpSummaryAll> findPage(ErpSummaryAll erpSummaryAll) {
return super.findPage(erpSummaryAll);
}
/**
* 查询列表数据
* @param erpSummaryAll 查询条件
*/
@Override
public List<ErpSummaryAll> findList(ErpSummaryAll erpSummaryAll) {
return super.findList(erpSummaryAll);
}
/**
* 保存数据(插入或更新)
* @param erpSummaryAll 数据对象
*/
@Override
@Transactional
public void save(ErpSummaryAll erpSummaryAll) {
super.save(erpSummaryAll);
}
/**
* 导入数据
* @param file 导入的数据文件
*/
@Transactional
public String importData(MultipartFile file) {
if (file == null){
throw new ServiceException(text("请选择导入的数据文件!"));
}
int successNum = 0; int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
try(ExcelImport ei = new ExcelImport(file, 2, 0)){
List<ErpSummaryAll> list = ei.getDataList(ErpSummaryAll.class);
for (ErpSummaryAll erpSummaryAll : list) {
try{
ValidatorUtils.validateWithException(erpSummaryAll);
this.save(erpSummaryAll);
successNum++;
successMsg.append("<br/>" + successNum + "、编号 " + erpSummaryAll.getId() + " 导入成功");
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、编号 " + erpSummaryAll.getId() + " 导入失败:";
if (e instanceof ConstraintViolationException){
ConstraintViolationException cve = (ConstraintViolationException)e;
for (ConstraintViolation<?> violation : cve.getConstraintViolations()) {
msg += Global.getText(violation.getMessage()) + " ("+violation.getPropertyPath()+")";
}
}else{
msg += e.getMessage();
}
failureMsg.append(msg);
logger.error(msg, e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
failureMsg.append(e.getMessage());
return failureMsg.toString();
}
if (failureNum > 0) {
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
throw new ServiceException(failureMsg.toString());
}else{
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
}
return successMsg.toString();
}
/**
* 更新状态
* @param erpSummaryAll 数据对象
*/
@Override
@Transactional
public void updateStatus(ErpSummaryAll erpSummaryAll) {
super.updateStatus(erpSummaryAll);
}
/**
* 删除数据
* @param erpSummaryAll 数据对象
*/
@Override
@Transactional
public void delete(ErpSummaryAll erpSummaryAll) {
erpSummaryAll.sqlMap().markIdDelete(); // 逻辑删除时标记ID值
super.delete(erpSummaryAll);
}
}

View File

@@ -0,0 +1,157 @@
package com.jeesite.modules.erp.web;
import java.util.List;
import com.jeesite.modules.app.utils.vDate;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.jeesite.common.config.Global;
import com.jeesite.common.collect.ListUtils;
import com.jeesite.common.entity.Page;
import com.jeesite.common.lang.DateUtils;
import com.jeesite.common.utils.excel.ExcelExport;
import com.jeesite.common.utils.excel.annotation.ExcelField.Type;
import org.springframework.web.multipart.MultipartFile;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.erp.entity.ErpSummaryAll;
import com.jeesite.modules.erp.service.ErpSummaryAllService;
/**
* 汇总信息Controller
*
* @author gaoxq
* @version 2025-12-09
*/
@Controller
@RequestMapping(value = "${adminPath}/erp/summaryAll")
public class ErpSummaryAllController extends BaseController {
private final ErpSummaryAllService erpSummaryAllService;
public ErpSummaryAllController(ErpSummaryAllService erpSummaryAllService) {
this.erpSummaryAllService = erpSummaryAllService;
}
/**
* 获取数据
*/
@ModelAttribute
public ErpSummaryAll get(String id, boolean isNewRecord) {
return erpSummaryAllService.get(id, isNewRecord);
}
/**
* 查询列表
*/
@RequiresPermissions("erp:summaryAll:view")
@RequestMapping(value = {"list", ""})
public String list(ErpSummaryAll erpSummaryAll, Model model) {
model.addAttribute("erpSummaryAll", erpSummaryAll);
return "modules/erp/erpSummaryAllList";
}
/**
* 查询列表数据
*/
@RequiresPermissions("erp:summaryAll:view")
@RequestMapping(value = "listData")
@ResponseBody
public Page<ErpSummaryAll> listData(ErpSummaryAll erpSummaryAll, HttpServletRequest request, HttpServletResponse response) {
erpSummaryAll.setPage(new Page<>(request, response));
Page<ErpSummaryAll> page = erpSummaryAllService.findPage(erpSummaryAll);
return page;
}
/**
* 查看编辑表单
*/
@RequiresPermissions("erp:summaryAll:view")
@RequestMapping(value = "form")
public String form(ErpSummaryAll erpSummaryAll, Model model) {
model.addAttribute("erpSummaryAll", erpSummaryAll);
return "modules/erp/erpSummaryAllForm";
}
/**
* 保存数据
*/
@RequiresPermissions("erp:summaryAll:edit")
@PostMapping(value = "save")
@ResponseBody
public String save(@Validated ErpSummaryAll erpSummaryAll) {
erpSummaryAllService.save(erpSummaryAll);
return renderResult(Global.TRUE, text("保存汇总信息成功!"));
}
/**
* 导出数据
*/
@RequiresPermissions("erp:summaryAll:view")
@RequestMapping(value = "exportData")
public void exportData(ErpSummaryAll erpSummaryAll, HttpServletResponse response) {
List<ErpSummaryAll> list = erpSummaryAllService.findList(erpSummaryAll);
String fileName = "汇总信息" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
try (ExcelExport ee = new ExcelExport("汇总信息", ErpSummaryAll.class)) {
ee.setDataList(list).write(response, fileName);
}
}
/**
* 下载模板
*/
@RequiresPermissions("erp:summaryAll:view")
@RequestMapping(value = "importTemplate")
public void importTemplate(HttpServletResponse response) {
ErpSummaryAll erpSummaryAll = new ErpSummaryAll();
List<ErpSummaryAll> list = ListUtils.newArrayList(erpSummaryAll);
String fileName = "汇总信息模板.xlsx";
try (ExcelExport ee = new ExcelExport("汇总信息", ErpSummaryAll.class, Type.IMPORT)) {
ee.setDataList(list).write(response, fileName);
}
}
/**
* 导入数据
*/
@ResponseBody
@RequiresPermissions("erp:summaryAll:edit")
@PostMapping(value = "importData")
public String importData(MultipartFile file) {
try {
String message = erpSummaryAllService.importData(file);
return renderResult(Global.TRUE, "posfull:" + message);
} catch (Exception ex) {
return renderResult(Global.FALSE, "posfull:" + ex.getMessage());
}
}
/**
* 删除数据
*/
@RequiresPermissions("erp:summaryAll:edit")
@RequestMapping(value = "delete")
@ResponseBody
public String delete(ErpSummaryAll erpSummaryAll) {
erpSummaryAllService.delete(erpSummaryAll);
return renderResult(Global.TRUE, text("删除汇总信息成功!"));
}
@RequestMapping(value = "listAll")
@ResponseBody
public List<ErpSummaryAll> listAll(ErpSummaryAll erpSummaryAll) {
erpSummaryAll.setCdate_gte(vDate.calculateStartCycleCode(erpSummaryAll.getFcycle()));
return erpSummaryAllService.findList(erpSummaryAll);
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jeesite.modules.erp.dao.ErpSummaryAllDao">
<!-- 查询数据
<select id="findList" resultType="ErpSummaryAll">
SELECT ${sqlMap.column.toSql()}
FROM ${sqlMap.table.toSql()}
<where>
${sqlMap.where.toSql()}
</where>
ORDER BY ${sqlMap.order.toSql()}
</select> -->
</mapper>

View File

@@ -1,19 +1,22 @@
<template> <template>
<!-- 调整卡片和图表容器尺寸让图表整体更小 --> <Card title="业务数据柱线图" style="width: 100%; height: 400px; margin: 20px 0;">
<a-card title="业务数据柱线图" style="width: 100%; height: 400px; margin: 20px 0;">
<div ref="chartDom" style="width: 100%; height: 300px;"></div> <div ref="chartDom" style="width: 100%; height: 300px;"></div>
</a-card> </Card>
</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 } from '@jeesite/core/components/Form';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
import { ErpSummaryAll, erpSummaryAllListAll } from '@jeesite/erp/api/erp/summaryAll';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
const listSummary = ref<ErpSummaryAll[]>([]);
const chartDom = ref<HTMLDivElement | null>(null); const chartDom = ref<HTMLDivElement | null>(null);
let myChart: echarts.ECharts | null = null; let myChart: echarts.ECharts | null = null;
// 封装公共的标签配置(避免重复代码)
const barLabelConfig = { const barLabelConfig = {
show: true, // 开启数值显示 show: true, // 开启数值显示
position: 'top', // 数值显示在柱子顶部 position: 'top', // 数值显示在柱子顶部
@@ -23,7 +26,7 @@ const barLabelConfig = {
color: '#333', // 数值字体颜色(深色更清晰) color: '#333', // 数值字体颜色(深色更清晰)
fontWeight: '500' // 字体加粗 fontWeight: '500' // 字体加粗
}, },
formatter: '{c} ' formatter: '{c} '
}; };
// 折线图标签配置 // 折线图标签配置
@@ -36,7 +39,18 @@ const lineLabelConfig = {
color: '#f5222d', color: '#f5222d',
fontWeight: '500' fontWeight: '500'
}, },
formatter: '{c} ' formatter: '{c} %'
};
const fetchList = async () => {
try {
const params = { fcycle: 'D' , ctype: '1' }
const result = await erpSummaryAllListAll(params);
listSummary.value = result || [];
} catch (error) {
console.error('获取数据列表失败:', error);
listSummary.value = [];
}
}; };
const initChart = () => { const initChart = () => {
@@ -45,7 +59,6 @@ const initChart = () => {
const option = { const option = {
title: { title: {
text: '月度销售额统计',
left: 'center', left: 'center',
textStyle: { fontSize: 18, color: '#333' } textStyle: { fontSize: 18, color: '#333' }
}, },
@@ -55,11 +68,10 @@ const initChart = () => {
textStyle: { fontSize: 12 } textStyle: { fontSize: 12 }
}, },
legend: { legend: {
data: ['产品A', '产品B','产品C', '销售额均值'], data: ['本期金额', '上期金额', '环比'],
top: 40, top: 10,
textStyle: { fontSize: 12 } textStyle: { fontSize: 12 }
}, },
// 调整图表内部边距,让绘图区域更紧凑
grid: { grid: {
left: '8%', left: '8%',
right: '8%', right: '8%',
@@ -69,45 +81,35 @@ const initChart = () => {
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'], data: listSummary.value.map(item => (item.cdate)),
axisLabel: { fontSize: 12 } axisLabel: { fontSize: 12 }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '销售额(元)', name: '交易金额(元)',
min: 0,
axisLabel: { fontSize: 12 } axisLabel: { fontSize: 12 }
}, },
series: [ series: [
{ {
name: '产品A', name: '本期金额',
type: 'bar', type: 'bar',
data: [50, 70, 65, 80, 90, 100], data: listSummary.value.map(item => (item.thisValue)),
itemStyle: { color: '#1890ff' }, itemStyle: { color: '#1890ff' },
barWidth: 25, // 调整柱子宽度(数值越小越细) barWidth: 25, // 调整柱子宽度(数值越小越细)
label: barLabelConfig // 引用公共标签配置 label: barLabelConfig // 引用公共标签配置
}, },
{ {
name: '产品B', name: '上期金额',
type: 'bar', type: 'bar',
data: [30, 45, 55, 70, 85, 95], data: listSummary.value.map(item => (item.prevValue)),
itemStyle: { color: '#52c41a' }, itemStyle: { color: '#52c41a' },
barWidth: 25, barWidth: 25,
label: barLabelConfig label: barLabelConfig
}, },
{
name: '产品C',
type: 'bar',
data: [40, 55, 60, 75, 80, 90],
itemStyle: { color: '#f5a623' },
barWidth: 25,
label: barLabelConfig
},
// 新增折线图系列(平滑曲线)
{ {
name: '销售额均值', name: '环比',
type: 'line', type: 'line',
data: [40, 56.67, 60, 75, 85, 95], // 三个月产品销售额的平均值 data: listSummary.value.map(item => (item.momRate)), // 三个月产品销售额的平均值
smooth: true, // 开启平滑曲线 smooth: true, // 开启平滑曲线
symbol: 'circle', // 拐点样式为圆形 symbol: 'circle', // 拐点样式为圆形
symbolSize: 8, // 拐点大小 symbolSize: 8, // 拐点大小
@@ -134,10 +136,9 @@ const initChart = () => {
myChart.setOption(option); myChart.setOption(option);
}; };
// 窗口缩放时自适应图表
const resizeChart = () => myChart?.resize(); const resizeChart = () => myChart?.resize();
onMounted(async () => {
onMounted(() => { await fetchList()
initChart(); initChart();
window.addEventListener('resize', resizeChart); window.addEventListener('resize', resizeChart);
}); });

View File

@@ -1,8 +1,7 @@
<template> <template>
<!-- 饼图卡片容器 --> <Card title="银行账户余额占比" style="width: 100%; height: 400px; margin: 20px 0;">
<a-card title="产品销售额占比图" style="width: 100%; height: 400px; margin: 20px 0;">
<div ref="chartDom" style="width: 100%; height: 300px;"></div> <div ref="chartDom" style="width: 100%; height: 300px;"></div>
</a-card> </Card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -35,16 +34,13 @@ const generateRandomColors = (count: number): string[] => {
'#7cb305', '#ff7a45', '#ff4d4f', '#6b778c', '#5d7092', '#91d5ff' '#7cb305', '#ff7a45', '#ff4d4f', '#6b778c', '#5d7092', '#91d5ff'
]; ];
// 如果需要的颜色数量小于等于预设库数量,随机选取不重复的颜色
if (count <= colorLibrary.length) { if (count <= colorLibrary.length) {
const shuffled = [...colorLibrary].sort(() => 0.5 - Math.random()); const shuffled = [...colorLibrary].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count); return shuffled.slice(0, count);
} }
// 如果需要更多颜色,生成随机的柔和颜色
const colors: string[] = []; const colors: string[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// 生成HSL颜色保证亮度和饱和度适中颜色美观
const hue = Math.floor(Math.random() * 360); const hue = Math.floor(Math.random() * 360);
const saturation = 70 + Math.floor(Math.random() * 20); // 70-90% const saturation = 70 + Math.floor(Math.random() * 20); // 70-90%
const lightness = 45 + Math.floor(Math.random() * 15); // 45-60% const lightness = 45 + Math.floor(Math.random() * 15); // 45-60%
@@ -71,18 +67,14 @@ const initChart = () => {
value: Number(item.currentBalance) || 0 value: Number(item.currentBalance) || 0
})); }));
// 生成随机颜色
const colors = generateRandomColors(pieData.length); const colors = generateRandomColors(pieData.length);
// 为每个数据项分配随机颜色
const pieDataWithColor = pieData.map((item, index) => ({ const pieDataWithColor = pieData.map((item, index) => ({
...item, ...item,
color: colors[index] color: colors[index]
})); }));
// 计算总销售额
const total = pieDataWithColor.reduce((sum, item) => sum + item.value, 0); const total = pieDataWithColor.reduce((sum, item) => sum + item.value, 0);
// 计算占比并格式化数据
const formattedData = pieDataWithColor.map(item => ({ const formattedData = pieDataWithColor.map(item => ({
name: item.name, name: item.name,
value: item.value, value: item.value,
@@ -92,19 +84,18 @@ const initChart = () => {
const option = { const option = {
title: { title: {
text: '银行账户余额占比',
left: 'center', left: 'center',
top: 10, top: 10,
textStyle: { fontSize: 16, color: '#333', fontWeight: 500 } textStyle: { fontSize: 16, color: '#333', fontWeight: 500 }
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}: {c} 元 ({d}%)', // 格式:名称: 数值 万元 (百分比) formatter: '{b}: {c} 元 ({d}%)',
textStyle: { fontSize: 12 } textStyle: { fontSize: 12 }
}, },
legend: { legend: {
orient: 'horizontal', // 水平布局 orient: 'horizontal', // 水平布局
bottom: 10, // 图例在底部 bottom: 5, // 图例在底部
left: 'center', left: 'center',
textStyle: { fontSize: 12, color: '#666' }, textStyle: { fontSize: 12, color: '#666' },
itemWidth: 12, itemWidth: 12,

View File

@@ -1,17 +1,17 @@
<template> <template>
<!-- 调整卡片和图表容器尺寸让图表整体更小 --> <!-- 主容器 -->
<Card> <div class="main-container">
<ChartBar /> <!-- 两列布局限制高度 + 垂直间距 -->
</Card> <div class="two-column-layout">
<div class="column left-column">
<Card> <ChartPie />
<ChartLine /> </div>
</Card> <div class="column right-column"></div>
</div>
<Card> <div class="chart-line-wrapper">
<ChartPie /> <ChartLine />
<ChartGauge /> </div>
</Card> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -19,11 +19,86 @@ import { ref, onMounted, onUnmounted } from 'vue';
import { Card } from 'ant-design-vue'; import { Card } from 'ant-design-vue';
import ChartPie from './components/ChartPie.vue'; import ChartPie from './components/ChartPie.vue';
import ChartBar from './components/ChartBar.vue';
import ChartLine from './components/ChartLine.vue'; import ChartLine from './components/ChartLine.vue';
import ChartGauge from './components/ChartGauge.vue';
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
</style> .main-container {
height: 90vh;
background-color: #e6f7ff;
overflow: auto;
padding: 2px;
box-sizing: border-box;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-track {
background: #f1f8ff;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #b3d9f2;
border-radius: 4px;
&:hover {
background: #8fc5e8;
}
}
scrollbar-width: thin;
scrollbar-color: #b3d9f2 #f1f8ff;
}
// 左右两列布局:限制高度 + 底部间距
.two-column-layout {
display: flex;
gap: 4px; // 两列水平间距
margin-bottom: 4px; // 与下方折线图的垂直间距(核心:缩小间距)
}
// 列通用样式
.column {
flex: 1; // 两列等分宽度
height: 100%;
display: flex;
flex-direction: column;
}
.left-column {
// 可选自定义样式
}
.right-column {
// 可选自定义样式
}
// 折线图容器:控制宽度 + 高度
.chart-line-wrapper {
width: 100%; // 撑满容器宽度(避免过宽)
margin-top: 0; // 覆盖默认间距
box-sizing: border-box;
}
// 卡片样式(优化适配列布局)
.content-card {
width: 100%;
height: 100%;
margin: 0;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
.column-content {
flex: 1;
padding: 4px 0;
overflow: auto;
}
}
// 长内容样式(测试用)
.long-content {
padding: 10px 0;
}
</style>

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2013-Now http://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, Page } from '@jeesite/core/api/model/baseModel';
import { UploadApiResult } from '@jeesite/core/api/sys/upload';
import { UploadFileParams } from '@jeesite/types/axios';
import { AxiosProgressEvent } from 'axios';
const { ctxPath, adminPath } = useGlobSetting();
export interface ErpSummaryAll extends BasicModel<ErpSummaryAll> {
createTime?: string; // 记录时间
cdate?: string; // 汇总日期
ctype: string; // 交易类型
thisValue?: number; // 当期金额
prevValue?: number; // 上期金额
momRate?: number; // 环比
fcycle: string; // 周期类型
}
export const erpSummaryAllList = (params?: ErpSummaryAll | any) =>
defHttp.get<ErpSummaryAll>({ url: adminPath + '/erp/summaryAll/list', params });
export const erpSummaryAllListAll = (params?: ErpSummaryAll | any) =>
defHttp.get<ErpSummaryAll[]>({ url: adminPath + '/erp/summaryAll/listAll', params });
export const erpSummaryAllListData = (params?: ErpSummaryAll | any) =>
defHttp.post<Page<ErpSummaryAll>>({ url: adminPath + '/erp/summaryAll/listData', params });
export const erpSummaryAllForm = (params?: ErpSummaryAll | any) =>
defHttp.get<ErpSummaryAll>({ url: adminPath + '/erp/summaryAll/form', params });
export const erpSummaryAllSave = (params?: any, data?: ErpSummaryAll | any) =>
defHttp.postJson<ErpSummaryAll>({ url: adminPath + '/erp/summaryAll/save', params, data });
export const erpSummaryAllImportData = (
params: UploadFileParams,
onUploadProgress: (progressEvent: AxiosProgressEvent) => void,
) =>
defHttp.uploadFile<UploadApiResult>(
{
url: ctxPath + adminPath + '/erp/summaryAll/importData',
onUploadProgress,
},
params,
);
export const erpSummaryAllDelete = (params?: ErpSummaryAll | any) =>
defHttp.get<ErpSummaryAll>({ url: adminPath + '/erp/summaryAll/delete', params });