新增前端vue
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.jeesite.modules.erp.web;
|
package com.jeesite.modules.erp.web;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ import com.jeesite.modules.erp.service.ErpAccountService;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 账户信息Controller
|
* 账户信息Controller
|
||||||
|
*
|
||||||
* @author gaoxq
|
* @author gaoxq
|
||||||
* @version 2025-11-29
|
* @version 2025-11-29
|
||||||
*/
|
*/
|
||||||
@@ -33,114 +35,120 @@ import com.jeesite.modules.erp.service.ErpAccountService;
|
|||||||
@RequestMapping(value = "${adminPath}/erp/account")
|
@RequestMapping(value = "${adminPath}/erp/account")
|
||||||
public class ErpAccountController extends BaseController {
|
public class ErpAccountController extends BaseController {
|
||||||
|
|
||||||
private final ErpAccountService erpAccountService;
|
private final ErpAccountService erpAccountService;
|
||||||
|
|
||||||
public ErpAccountController(ErpAccountService erpAccountService) {
|
public ErpAccountController(ErpAccountService erpAccountService) {
|
||||||
this.erpAccountService = erpAccountService;
|
this.erpAccountService = erpAccountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数据
|
|
||||||
*/
|
|
||||||
@ModelAttribute
|
|
||||||
public ErpAccount get(String accountId, boolean isNewRecord) {
|
|
||||||
return erpAccountService.get(accountId, isNewRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询列表
|
|
||||||
*/
|
|
||||||
@RequiresPermissions("erp:account:view")
|
|
||||||
@RequestMapping(value = {"list", ""})
|
|
||||||
public String list(ErpAccount erpAccount, Model model) {
|
|
||||||
model.addAttribute("erpAccount", erpAccount);
|
|
||||||
return "modules/erp/erpAccountList";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询列表数据
|
|
||||||
*/
|
|
||||||
@RequiresPermissions("erp:account:view")
|
|
||||||
@RequestMapping(value = "listData")
|
|
||||||
@ResponseBody
|
|
||||||
public Page<ErpAccount> listData(ErpAccount erpAccount, HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
erpAccount.setPage(new Page<>(request, response));
|
|
||||||
Page<ErpAccount> page = erpAccountService.findPage(erpAccount);
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看编辑表单
|
* 获取数据
|
||||||
*/
|
*/
|
||||||
@RequiresPermissions("erp:account:view")
|
@ModelAttribute
|
||||||
@RequestMapping(value = "form")
|
public ErpAccount get(String accountId, boolean isNewRecord) {
|
||||||
public String form(ErpAccount erpAccount, Model model) {
|
return erpAccountService.get(accountId, isNewRecord);
|
||||||
model.addAttribute("erpAccount", erpAccount);
|
}
|
||||||
return "modules/erp/erpAccountForm";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存数据
|
* 查询列表
|
||||||
*/
|
*/
|
||||||
@RequiresPermissions("erp:account:edit")
|
@RequiresPermissions("erp:account:view")
|
||||||
@PostMapping(value = "save")
|
@RequestMapping(value = {"list", ""})
|
||||||
@ResponseBody
|
public String list(ErpAccount erpAccount, Model model) {
|
||||||
public String save(@Validated ErpAccount erpAccount) {
|
model.addAttribute("erpAccount", erpAccount);
|
||||||
erpAccountService.save(erpAccount);
|
return "modules/erp/erpAccountList";
|
||||||
return renderResult(Global.TRUE, text("保存账户信息成功!"));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出数据
|
* 查询列表数据
|
||||||
*/
|
*/
|
||||||
@RequiresPermissions("erp:account:view")
|
@RequiresPermissions("erp:account:view")
|
||||||
@RequestMapping(value = "exportData")
|
@RequestMapping(value = "listData")
|
||||||
public void exportData(ErpAccount erpAccount, HttpServletResponse response) {
|
@ResponseBody
|
||||||
List<ErpAccount> list = erpAccountService.findList(erpAccount);
|
public Page<ErpAccount> listData(ErpAccount erpAccount, HttpServletRequest request, HttpServletResponse response) {
|
||||||
String fileName = "账户信息" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
|
erpAccount.setPage(new Page<>(request, response));
|
||||||
try(ExcelExport ee = new ExcelExport("账户信息", ErpAccount.class)){
|
Page<ErpAccount> page = erpAccountService.findPage(erpAccount);
|
||||||
ee.setDataList(list).write(response, fileName);
|
return page;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载模板
|
* 查看编辑表单
|
||||||
*/
|
*/
|
||||||
@RequiresPermissions("erp:account:view")
|
@RequiresPermissions("erp:account:view")
|
||||||
@RequestMapping(value = "importTemplate")
|
@RequestMapping(value = "form")
|
||||||
public void importTemplate(HttpServletResponse response) {
|
public String form(ErpAccount erpAccount, Model model) {
|
||||||
ErpAccount erpAccount = new ErpAccount();
|
model.addAttribute("erpAccount", erpAccount);
|
||||||
List<ErpAccount> list = ListUtils.newArrayList(erpAccount);
|
return "modules/erp/erpAccountForm";
|
||||||
String fileName = "账户信息模板.xlsx";
|
}
|
||||||
try(ExcelExport ee = new ExcelExport("账户信息", ErpAccount.class, Type.IMPORT)){
|
|
||||||
ee.setDataList(list).write(response, fileName);
|
/**
|
||||||
}
|
* 保存数据
|
||||||
}
|
*/
|
||||||
|
@RequiresPermissions("erp:account:edit")
|
||||||
|
@PostMapping(value = "save")
|
||||||
|
@ResponseBody
|
||||||
|
public String save(@Validated ErpAccount erpAccount) {
|
||||||
|
erpAccountService.save(erpAccount);
|
||||||
|
return renderResult(Global.TRUE, text("保存账户信息成功!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出数据
|
||||||
|
*/
|
||||||
|
@RequiresPermissions("erp:account:view")
|
||||||
|
@RequestMapping(value = "exportData")
|
||||||
|
public void exportData(ErpAccount erpAccount, HttpServletResponse response) {
|
||||||
|
List<ErpAccount> list = erpAccountService.findList(erpAccount);
|
||||||
|
String fileName = "账户信息" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
|
||||||
|
try (ExcelExport ee = new ExcelExport("账户信息", ErpAccount.class)) {
|
||||||
|
ee.setDataList(list).write(response, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载模板
|
||||||
|
*/
|
||||||
|
@RequiresPermissions("erp:account:view")
|
||||||
|
@RequestMapping(value = "importTemplate")
|
||||||
|
public void importTemplate(HttpServletResponse response) {
|
||||||
|
ErpAccount erpAccount = new ErpAccount();
|
||||||
|
List<ErpAccount> list = ListUtils.newArrayList(erpAccount);
|
||||||
|
String fileName = "账户信息模板.xlsx";
|
||||||
|
try (ExcelExport ee = new ExcelExport("账户信息", ErpAccount.class, Type.IMPORT)) {
|
||||||
|
ee.setDataList(list).write(response, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*/
|
||||||
|
@ResponseBody
|
||||||
|
@RequiresPermissions("erp:account:edit")
|
||||||
|
@PostMapping(value = "importData")
|
||||||
|
public String importData(MultipartFile file) {
|
||||||
|
try {
|
||||||
|
String message = erpAccountService.importData(file);
|
||||||
|
return renderResult(Global.TRUE, "posfull:" + message);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return renderResult(Global.FALSE, "posfull:" + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除数据
|
||||||
|
*/
|
||||||
|
@RequiresPermissions("erp:account:edit")
|
||||||
|
@RequestMapping(value = "delete")
|
||||||
|
@ResponseBody
|
||||||
|
public String delete(ErpAccount erpAccount) {
|
||||||
|
erpAccountService.delete(erpAccount);
|
||||||
|
return renderResult(Global.TRUE, text("删除账户信息成功!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "listAll")
|
||||||
|
@ResponseBody
|
||||||
|
public List<ErpAccount> listAll(ErpAccount erpAccount) {
|
||||||
|
return erpAccountService.findList(erpAccount);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入数据
|
|
||||||
*/
|
|
||||||
@ResponseBody
|
|
||||||
@RequiresPermissions("erp:account:edit")
|
|
||||||
@PostMapping(value = "importData")
|
|
||||||
public String importData(MultipartFile file) {
|
|
||||||
try {
|
|
||||||
String message = erpAccountService.importData(file);
|
|
||||||
return renderResult(Global.TRUE, "posfull:"+message);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return renderResult(Global.FALSE, "posfull:"+ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除数据
|
|
||||||
*/
|
|
||||||
@RequiresPermissions("erp:account:edit")
|
|
||||||
@RequestMapping(value = "delete")
|
|
||||||
@ResponseBody
|
|
||||||
public String delete(ErpAccount erpAccount) {
|
|
||||||
erpAccountService.delete(erpAccount);
|
|
||||||
return renderResult(Global.TRUE, text("删除账户信息成功!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 仪表盘卡片容器 -->
|
||||||
|
<a-card title="销售目标达成率" style="width: 100%; height: 400px; margin: 20px 0;">
|
||||||
|
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
const chartDom = ref<HTMLDivElement | null>(null);
|
||||||
|
let myChart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 格式化百分比显示
|
||||||
|
const formatPercent = (value: number) => {
|
||||||
|
return `${value.toFixed(1)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartDom.value) return;
|
||||||
|
myChart = echarts.init(chartDom.value);
|
||||||
|
|
||||||
|
// 仪表盘核心数据
|
||||||
|
const targetRate = 85.6; // 销售目标达成率(可替换为真实数据)
|
||||||
|
const maxValue = 100; // 仪表盘最大值
|
||||||
|
const splitNumber = 10; // 刻度分割数
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
title: {
|
||||||
|
text: '上半年销售目标达成率',
|
||||||
|
left: 'center',
|
||||||
|
top: 10,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#2c3e50',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontFamily: 'Inter, "Microsoft YaHei", sans-serif'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: ({ data }: any) => `
|
||||||
|
<div style="padding: 8px 12px;">
|
||||||
|
<div style="font-size: 14px; color: #2c3e50; font-weight: 500;">${data.name}</div>
|
||||||
|
<div style="font-size: 16px; color: #1677ff; font-weight: 600; margin-top: 4px;">
|
||||||
|
${formatPercent(data.value)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
borderColor: 'rgba(22, 119, 255, 0.1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: 'Inter, "Microsoft YaHei", sans-serif'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 仪表盘系列配置
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'gauge',
|
||||||
|
name: '达成率',
|
||||||
|
startAngle: 90, // 仪表盘起始角度
|
||||||
|
endAngle: -270, // 仪表盘结束角度(完整环形)
|
||||||
|
min: 0, // 最小值
|
||||||
|
max: maxValue, // 最大值
|
||||||
|
splitNumber: splitNumber, // 刻度分割数量
|
||||||
|
radius: '80%', // 仪表盘半径
|
||||||
|
center: ['50%', '55%'],// 仪表盘中心位置
|
||||||
|
// 指针样式
|
||||||
|
pointer: {
|
||||||
|
show: true,
|
||||||
|
length: '75%', // 指针长度
|
||||||
|
width: 8, // 指针宽度
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 1,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#1677ff' },
|
||||||
|
{ offset: 1, color: '#4096ff' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
shadowColor: 'rgba(22, 119, 255, 0.3)',
|
||||||
|
shadowBlur: 8,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 刻度线样式 - 渐变分段
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 24, // 仪表盘轴线宽度
|
||||||
|
// 分段颜色(更细腻的渐变效果)
|
||||||
|
color: [
|
||||||
|
[0.3, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#ff4d4f' },
|
||||||
|
{ offset: 1, color: '#ff7875' }
|
||||||
|
])], // 0-30% 红色系
|
||||||
|
[0.7, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#faad14' },
|
||||||
|
{ offset: 1, color: '#ffc53d' }
|
||||||
|
])], // 30-70% 黄色系
|
||||||
|
[1, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#52c41a' },
|
||||||
|
{ offset: 1, color: '#73d13d' }
|
||||||
|
])] // 70-100% 绿色系
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 刻度标签样式
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
distance: 35, // 标签距离轴线的距离
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#4e5969',
|
||||||
|
fontWeight: 500,
|
||||||
|
formatter: (value: number) => `${value}%`,
|
||||||
|
fontFamily: 'Inter, "Microsoft YaHei", sans-serif'
|
||||||
|
},
|
||||||
|
// 分隔线样式
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
length: 20, // 分隔线长度
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
color: '#e5e6eb',
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
shadowBlur: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 小刻度样式
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
length: 10, // 小刻度长度
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: '#f0f2f5'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 仪表盘标题(内部)
|
||||||
|
title: {
|
||||||
|
show: true,
|
||||||
|
offsetCenter: [0, '-35%'], // 标题位置(相对中心)
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#2c3e50',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontFamily: 'Inter, "Microsoft YaHei", sans-serif'
|
||||||
|
},
|
||||||
|
// 仪表盘详情(数值展示)
|
||||||
|
detail: {
|
||||||
|
show: true,
|
||||||
|
offsetCenter: [0, '0%'], // 数值位置(相对中心)
|
||||||
|
fontSize: 32, // 数值字体大小
|
||||||
|
fontWeight: 700,
|
||||||
|
formatter: (value: number) => formatPercent(value),
|
||||||
|
// 更精致的数值背景
|
||||||
|
backgroundColor: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(255, 255, 255, 0.95)' },
|
||||||
|
{ offset: 1, color: 'rgba(255, 255, 255, 0.85)' }
|
||||||
|
]),
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: [12, 24],
|
||||||
|
borderColor: 'rgba(22, 119, 255, 0.1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
shadowColor: 'rgba(22, 119, 255, 0.08)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#1677ff' },
|
||||||
|
{ offset: 1, color: '#4096ff' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
// 数据值
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: targetRate,
|
||||||
|
name: '销售达成率'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 悬浮高亮效果
|
||||||
|
emphasis: {
|
||||||
|
pointer: {
|
||||||
|
length: '80%',
|
||||||
|
width: 10,
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 12,
|
||||||
|
shadowColor: 'rgba(22, 119, 255, 0.4)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
fontSize: 36,
|
||||||
|
shadowBlur: 15,
|
||||||
|
shadowColor: 'rgba(22, 119, 255, 0.2)',
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 动画效果
|
||||||
|
animationDuration: 1500,
|
||||||
|
animationEasing: 'cubicOut',
|
||||||
|
animationDelay: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 窗口缩放自适应
|
||||||
|
const resizeChart = () => myChart?.resize();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', resizeChart);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeChart);
|
||||||
|
myChart?.dispose();
|
||||||
|
myChart = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ant-card) {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid #f5f7fa;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card:hover) {
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card-head) {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #f5f7fa 100%);
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-family: 'Inter', "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化仪表盘文字样式 */
|
||||||
|
:deep(.echarts-text) {
|
||||||
|
font-family: 'Inter', "Microsoft YaHei", sans-serif;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化tooltip样式 */
|
||||||
|
:deep(.echarts-tooltip) {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
border: 1px solid rgba(22, 119, 255, 0.1);
|
||||||
|
padding: 0;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 仪表盘数值背景优化 */
|
||||||
|
:deep(.echarts-gauge-detail) {
|
||||||
|
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.08);
|
||||||
|
border-radius: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 仪表盘轴线美化 */
|
||||||
|
:deep(.echarts-gauge-axis-line) {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 刻度线美化 */
|
||||||
|
:deep(.echarts-gauge-split-line) {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 调整卡片和图表容器尺寸,让图表整体更小 -->
|
||||||
|
<a-card title="业务数据柱线图" style="width: 100%; height: 400px; margin: 20px 0;">
|
||||||
|
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
const chartDom = ref<HTMLDivElement | null>(null);
|
||||||
|
let myChart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 封装公共的标签配置(避免重复代码)
|
||||||
|
const barLabelConfig = {
|
||||||
|
show: true, // 开启数值显示
|
||||||
|
position: 'top', // 数值显示在柱子顶部
|
||||||
|
distance: 3, // 数值与柱子顶部的间距(px)
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 11, // 数值字体大小
|
||||||
|
color: '#333', // 数值字体颜色(深色更清晰)
|
||||||
|
fontWeight: '500' // 字体加粗
|
||||||
|
},
|
||||||
|
formatter: '{c} 万'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 折线图标签配置
|
||||||
|
const lineLabelConfig = {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 5,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#f5222d',
|
||||||
|
fontWeight: '500'
|
||||||
|
},
|
||||||
|
formatter: '{c} 万'
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartDom.value) return;
|
||||||
|
myChart = echarts.init(chartDom.value);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '月度销售额统计',
|
||||||
|
left: 'center',
|
||||||
|
textStyle: { fontSize: 18, color: '#333' }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['产品A', '产品B','产品C', '销售额均值'],
|
||||||
|
top: 40,
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
// 调整图表内部边距,让绘图区域更紧凑
|
||||||
|
grid: {
|
||||||
|
left: '8%',
|
||||||
|
right: '8%',
|
||||||
|
bottom: '10%',
|
||||||
|
top: '20%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||||
|
axisLabel: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '销售额(万元)',
|
||||||
|
min: 0,
|
||||||
|
axisLabel: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '产品A',
|
||||||
|
type: 'bar',
|
||||||
|
data: [50, 70, 65, 80, 90, 100],
|
||||||
|
itemStyle: { color: '#1890ff' },
|
||||||
|
barWidth: 25, // 调整柱子宽度(数值越小越细)
|
||||||
|
label: barLabelConfig // 引用公共标签配置
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '产品B',
|
||||||
|
type: 'bar',
|
||||||
|
data: [30, 45, 55, 70, 85, 95],
|
||||||
|
itemStyle: { color: '#52c41a' },
|
||||||
|
barWidth: 25,
|
||||||
|
label: barLabelConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '产品C',
|
||||||
|
type: 'bar',
|
||||||
|
data: [40, 55, 60, 75, 80, 90],
|
||||||
|
itemStyle: { color: '#f5a623' },
|
||||||
|
barWidth: 25,
|
||||||
|
label: barLabelConfig
|
||||||
|
},
|
||||||
|
// 新增折线图系列(平滑曲线)
|
||||||
|
{
|
||||||
|
name: '销售额均值',
|
||||||
|
type: 'line',
|
||||||
|
data: [40, 56.67, 60, 75, 85, 95], // 三个月产品销售额的平均值
|
||||||
|
smooth: true, // 开启平滑曲线
|
||||||
|
symbol: 'circle', // 拐点样式为圆形
|
||||||
|
symbolSize: 8, // 拐点大小
|
||||||
|
lineStyle: {
|
||||||
|
width: 3, // 线条宽度
|
||||||
|
color: '#f5222d' // 线条颜色
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#f5222d', // 拐点填充色
|
||||||
|
borderColor: '#fff', // 拐点边框色
|
||||||
|
borderWidth: 2 // 拐点边框宽度
|
||||||
|
},
|
||||||
|
label: lineLabelConfig, // 折线数值标签
|
||||||
|
emphasis: {
|
||||||
|
// 高亮状态下的样式
|
||||||
|
itemStyle: {
|
||||||
|
symbolSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 窗口缩放时自适应图表
|
||||||
|
const resizeChart = () => myChart?.resize();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', resizeChart);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeChart);
|
||||||
|
myChart?.dispose(); // 销毁图表,避免内存泄漏
|
||||||
|
myChart = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ant-card) {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可选:优化数值标签的样式(如果需要) */
|
||||||
|
:deep(.echarts-label) {
|
||||||
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化折线图tooltip样式 */
|
||||||
|
:deep(.echarts-tooltip) {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 饼图卡片容器 -->
|
||||||
|
<a-card title="产品销售额占比图" style="width: 100%; height: 400px; margin: 20px 0;">
|
||||||
|
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
import { ErpAccount, erpAccountListAll } from '@jeesite/erp/api/erp/account';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { log } from 'console';
|
||||||
|
|
||||||
|
const listAccount = ref<ErpAccount[]>([]);
|
||||||
|
|
||||||
|
const chartDom = ref<HTMLDivElement | null>(null);
|
||||||
|
let myChart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 饼图数据格式化函数
|
||||||
|
const formatPercent = (value: number) => {
|
||||||
|
return `${value.toFixed(1)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机的美观颜色
|
||||||
|
* @param count 需要生成的颜色数量
|
||||||
|
* @returns 颜色数组
|
||||||
|
*/
|
||||||
|
const generateRandomColors = (count: number): string[] => {
|
||||||
|
// 预设的美观颜色库(可扩展)
|
||||||
|
const colorLibrary = [
|
||||||
|
'#1890ff', '#52c41a', '#f5a623', '#fa8c16', '#722ed1', '#eb2f96',
|
||||||
|
'#13c2c2', '#2f54eb', '#f7ba1e', '#f5222d', '#8543e0', '#0fc6c2',
|
||||||
|
'#7cb305', '#ff7a45', '#ff4d4f', '#6b778c', '#5d7092', '#91d5ff'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果需要的颜色数量小于等于预设库数量,随机选取不重复的颜色
|
||||||
|
if (count <= colorLibrary.length) {
|
||||||
|
const shuffled = [...colorLibrary].sort(() => 0.5 - Math.random());
|
||||||
|
return shuffled.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要更多颜色,生成随机的柔和颜色
|
||||||
|
const colors: string[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
// 生成HSL颜色,保证亮度和饱和度适中,颜色美观
|
||||||
|
const hue = Math.floor(Math.random() * 360);
|
||||||
|
const saturation = 70 + Math.floor(Math.random() * 20); // 70-90%
|
||||||
|
const lightness = 45 + Math.floor(Math.random() * 15); // 45-60%
|
||||||
|
colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
|
||||||
|
}
|
||||||
|
return colors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchList = async () => {
|
||||||
|
try {
|
||||||
|
const result = await erpAccountListAll();
|
||||||
|
listAccount.value = result || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取账号列表失败:', error);
|
||||||
|
listAccount.value = []; // 异常时置空列表,显示空状态
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartDom.value) return;
|
||||||
|
myChart = echarts.init(chartDom.value);
|
||||||
|
const pieData = listAccount.value.map(item => ({
|
||||||
|
name: item.accountName || '未知账户',
|
||||||
|
value: Number(item.currentBalance) || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 生成随机颜色
|
||||||
|
const colors = generateRandomColors(pieData.length);
|
||||||
|
|
||||||
|
// 为每个数据项分配随机颜色
|
||||||
|
const pieDataWithColor = pieData.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
color: colors[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 计算总销售额
|
||||||
|
const total = pieDataWithColor.reduce((sum, item) => sum + item.value, 0);
|
||||||
|
// 计算占比并格式化数据
|
||||||
|
const formattedData = pieDataWithColor.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value,
|
||||||
|
percent: (item.value / total) * 100,
|
||||||
|
color: item.color
|
||||||
|
}));
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '银行账户余额占比',
|
||||||
|
left: 'center',
|
||||||
|
top: 10,
|
||||||
|
textStyle: { fontSize: 16, color: '#333', fontWeight: 500 }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}: {c} 元 ({d}%)', // 格式:名称: 数值 万元 (百分比)
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'horizontal', // 水平布局
|
||||||
|
bottom: 10, // 图例在底部
|
||||||
|
left: 'center',
|
||||||
|
textStyle: { fontSize: 12, color: '#666' },
|
||||||
|
itemWidth: 12,
|
||||||
|
itemHeight: 12,
|
||||||
|
itemGap: 30, // 图例项之间的间距(默认10),调大到30
|
||||||
|
padding: [10, 20], // 图例内边距 [上, 右, 下, 左]
|
||||||
|
spacing: [20, 10], // 图例组件之间的间距
|
||||||
|
},
|
||||||
|
// 饼图系列配置
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '账户金额',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'], // 内环和外环半径,实现环形饼图效果
|
||||||
|
center: ['50%', '45%'], // 微调饼图位置,给图例留出更多空间
|
||||||
|
avoidLabelOverlap: true, // 开启避免标签重叠
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 8, // 扇形圆角
|
||||||
|
borderColor: '#fff', // 扇形边框白色
|
||||||
|
borderWidth: 2 // 边框宽度
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true, // 显示标签
|
||||||
|
position: 'outside', // 标签在饼图外部
|
||||||
|
fontSize: 11,
|
||||||
|
formatter: (params: any) => {
|
||||||
|
return `${params.name} ${formatPercent(params.percent)}`;
|
||||||
|
},
|
||||||
|
color: '#333',
|
||||||
|
distance: 20, // 标签离饼图的距离,避免和图例重叠
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: true, // 显示标签连接线
|
||||||
|
length: 20, // 第一段线长度(加长避免重叠)
|
||||||
|
length2: 15, // 第二段线长度
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
color: '#999'
|
||||||
|
},
|
||||||
|
smooth: 0.2, // 连接线轻微平滑,避免生硬
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
// 悬浮高亮效果
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 数据项(指定随机颜色)
|
||||||
|
data: formattedData.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value,
|
||||||
|
itemStyle: { color: item.color }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 窗口缩放自适应
|
||||||
|
const resizeChart = () => myChart?.resize();
|
||||||
|
|
||||||
|
onMounted(async() => {
|
||||||
|
await fetchList()
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', resizeChart);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeChart);
|
||||||
|
myChart?.dispose();
|
||||||
|
myChart = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ant-card) {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化饼图图例样式 - 增强间距效果 */
|
||||||
|
:deep(.ant-card .echarts-legend-item) {
|
||||||
|
margin: 0 8px !important; /* 增加左右外边距 */
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化图例文字和图标对齐 */
|
||||||
|
:deep(.echarts-legend-text) {
|
||||||
|
margin-left: 6px; /* 文字和图例图标之间的间距 */
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化饼图标签字体 */
|
||||||
|
:deep(.echarts-text) {
|
||||||
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬浮高亮效果优化 */
|
||||||
|
:deep(.echarts-tooltip) {
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -121,4 +121,4 @@ onUnmounted(() => {
|
|||||||
:deep(.echarts-label) {
|
:deep(.echarts-label) {
|
||||||
font-family: 'Microsoft YaHei', sans-serif;
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
29
web-vue/packages/biz/views/biz/dataReport/erp/index.vue
Normal file
29
web-vue/packages/biz/views/biz/dataReport/erp/index.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 调整卡片和图表容器尺寸,让图表整体更小 -->
|
||||||
|
<Card>
|
||||||
|
<ChartBar />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<ChartLine />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<ChartPie />
|
||||||
|
<ChartGauge />
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import ChartPie from './components/ChartPie.vue';
|
||||||
|
import ChartBar from './components/ChartBar.vue';
|
||||||
|
import ChartLine from './components/ChartLine.vue';
|
||||||
|
import ChartGauge from './components/ChartGauge.vue';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
</style>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workbench-sidebar">
|
<div class="workbench-sidebar">
|
||||||
<QuickLogin :loading="loading" class="workbench-card" />
|
<QuickLogin :loading="loading" class="workbench-card" />
|
||||||
<WarningAlert :loading="loading" />
|
<WarningAlert :loading="loading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
@@ -121,4 +121,4 @@ onMounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export interface ErpAccount extends BasicModel<ErpAccount> {
|
|||||||
|
|
||||||
export const erpAccountList = (params?: ErpAccount | any) =>
|
export const erpAccountList = (params?: ErpAccount | any) =>
|
||||||
defHttp.get<ErpAccount>({ url: adminPath + '/erp/account/list', params });
|
defHttp.get<ErpAccount>({ url: adminPath + '/erp/account/list', params });
|
||||||
|
|
||||||
|
export const erpAccountListAll = (params?: ErpAccount | any) =>
|
||||||
|
defHttp.get<ErpAccount[]>({ url: adminPath + '/erp/account/listAll', params });
|
||||||
|
|
||||||
export const erpAccountListData = (params?: ErpAccount | any) =>
|
export const erpAccountListData = (params?: ErpAccount | any) =>
|
||||||
defHttp.post<Page<ErpAccount>>({ url: adminPath + '/erp/account/listData', params });
|
defHttp.post<Page<ErpAccount>>({ url: adminPath + '/erp/account/listData', params });
|
||||||
|
|||||||
Reference in New Issue
Block a user