新增前端vue

This commit is contained in:
2025-12-10 15:07:01 +08:00
parent 2de5b51e07
commit 62457d9b24
15 changed files with 1133 additions and 554 deletions

View File

@@ -51,8 +51,6 @@ public class vDate {
}
public static String dsValue() {
LocalDate currentDate = LocalDate.now();
// 格式化日期为yyyymmdd
@@ -136,13 +134,13 @@ public class vDate {
return switch (cycleType) {
case "D" ->
// 日最近30天格式 yyyy-MM-dd
baseDate.minusDays(30).format(DAY_FORMATTER);
baseDate.minusDays(14).format(DAY_FORMATTER);
case "M" ->
// 月最近1年格式 yyyy-MM
baseDate.minusYears(1).format(MONTH_FORMATTER);
case "Q" ->
// 季度最近3年格式 yyyy-Qx
getQuarterCycleCode(baseDate.minusYears(3));
getQuarterCycleCode(baseDate.minusYears(2));
case "Y" ->
// 年最近6年格式 yyyy
String.valueOf(baseDate.minusYears(6).getYear());

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.ErpExpInc;
/**
* 收支信息DAO接口
* @author gaoxq
* @version 2025-12-10
*/
@MyBatisDao(dataSourceName="work")
public interface ErpExpIncDao extends CrudDao<ErpExpInc> {
}

View File

@@ -0,0 +1,73 @@
package com.jeesite.modules.erp.entity;
import java.io.Serializable;
import java.util.Date;
import com.jeesite.common.mybatis.annotation.JoinTable;
import com.jeesite.common.mybatis.annotation.JoinTable.Type;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Size;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 收支信息Entity
*
* @author gaoxq
* @version 2025-12-10
*/
@EqualsAndHashCode(callSuper = true)
@Table(name = "erp_exp_inc", alias = "a", label = "收支信息信息", columns = {
@Column(name = "create_time", attrName = "createTime", label = "记录时间", isUpdateForce = true),
@Column(name = "id", attrName = "id", label = "主键ID", isPK = true),
@Column(name = "account_name", attrName = "accountName", label = "account_name", queryType = QueryType.LIKE),
@Column(name = "stat_date", attrName = "statDate", label = "stat_date"),
@Column(name = "cycle_type", attrName = "cycleType", label = "cycle_type"),
@Column(name = "income_amount", attrName = "incomeAmount", label = "income_amount", isUpdateForce = true),
@Column(name = "expense_amount", attrName = "expenseAmount", label = "expense_amount", isUpdateForce = true),
}, orderBy = "a.stat_date"
)
@Data
public class ErpExpInc extends DataEntity<ErpExpInc> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Date createTime; // 记录时间
private String accountName; // account_name
private String statDate; // stat_date
private String cycleType; // cycle_type
private Double incomeAmount; // income_amount
private Double expenseAmount; // expense_amount
public ErpExpInc() {
this(null);
}
public ErpExpInc(String id) {
super(id);
}
public String getStatDate_gte() {
return sqlMap.getWhere().getValue("stat_date", QueryType.GTE);
}
public void setStatDate_gte(String statDate) {
sqlMap.getWhere().and("stat_date", QueryType.GTE, statDate);
}
public String getStatDate_lte() {
return sqlMap.getWhere().getValue("stat_date", QueryType.LTE);
}
public void setStatDate_lte(String statDate) {
sqlMap.getWhere().and("stat_date", QueryType.LTE, statDate);
}
}

View File

@@ -26,7 +26,7 @@ import java.io.Serial;
@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 = "c_type", attrName = "ctype", label = "交易类型"),
@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),

View File

@@ -0,0 +1,78 @@
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.ErpExpInc;
import com.jeesite.modules.erp.dao.ErpExpIncDao;
/**
* 收支信息Service
* @author gaoxq
* @version 2025-12-10
*/
@Service
public class ErpExpIncService extends CrudService<ErpExpIncDao, ErpExpInc> {
/**
* 获取单条数据
* @param erpExpInc 主键
*/
@Override
public ErpExpInc get(ErpExpInc erpExpInc) {
return super.get(erpExpInc);
}
/**
* 查询分页数据
* @param erpExpInc 查询条件
* @param erpExpInc page 分页对象
*/
@Override
public Page<ErpExpInc> findPage(ErpExpInc erpExpInc) {
return super.findPage(erpExpInc);
}
/**
* 查询列表数据
* @param erpExpInc 查询条件
*/
@Override
public List<ErpExpInc> findList(ErpExpInc erpExpInc) {
return super.findList(erpExpInc);
}
/**
* 保存数据(插入或更新)
* @param erpExpInc 数据对象
*/
@Override
@Transactional
public void save(ErpExpInc erpExpInc) {
super.save(erpExpInc);
}
/**
* 更新状态
* @param erpExpInc 数据对象
*/
@Override
@Transactional
public void updateStatus(ErpExpInc erpExpInc) {
super.updateStatus(erpExpInc);
}
/**
* 删除数据
* @param erpExpInc 数据对象
*/
@Override
@Transactional
public void delete(ErpExpInc erpExpInc) {
super.delete(erpExpInc);
}
}

View File

@@ -0,0 +1,110 @@
package com.jeesite.modules.erp.web;
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.entity.Page;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.erp.entity.ErpExpInc;
import com.jeesite.modules.erp.service.ErpExpIncService;
import java.util.List;
/**
* 收支信息Controller
*
* @author gaoxq
* @version 2025-12-10
*/
@Controller
@RequestMapping(value = "${adminPath}/erp/expInc")
public class ErpExpIncController extends BaseController {
private final ErpExpIncService erpExpIncService;
public ErpExpIncController(ErpExpIncService erpExpIncService) {
this.erpExpIncService = erpExpIncService;
}
/**
* 获取数据
*/
@ModelAttribute
public ErpExpInc get(String id, boolean isNewRecord) {
return erpExpIncService.get(id, isNewRecord);
}
/**
* 查询列表
*/
@RequiresPermissions("erp:expInc:view")
@RequestMapping(value = {"list", ""})
public String list(ErpExpInc erpExpInc, Model model) {
model.addAttribute("erpExpInc", erpExpInc);
return "modules/erp/erpExpIncList";
}
/**
* 查询列表数据
*/
@RequiresPermissions("erp:expInc:view")
@RequestMapping(value = "listData")
@ResponseBody
public Page<ErpExpInc> listData(ErpExpInc erpExpInc, HttpServletRequest request, HttpServletResponse response) {
erpExpInc.setPage(new Page<>(request, response));
Page<ErpExpInc> page = erpExpIncService.findPage(erpExpInc);
return page;
}
/**
* 查看编辑表单
*/
@RequiresPermissions("erp:expInc:view")
@RequestMapping(value = "form")
public String form(ErpExpInc erpExpInc, Model model) {
model.addAttribute("erpExpInc", erpExpInc);
return "modules/erp/erpExpIncForm";
}
/**
* 保存数据
*/
@RequiresPermissions("erp:expInc:edit")
@PostMapping(value = "save")
@ResponseBody
public String save(@Validated ErpExpInc erpExpInc) {
erpExpIncService.save(erpExpInc);
return renderResult(Global.TRUE, text("保存收支信息成功!"));
}
/**
* 删除数据
*/
@RequiresPermissions("erp:expInc:edit")
@RequestMapping(value = "delete")
@ResponseBody
public String delete(ErpExpInc erpExpInc) {
erpExpIncService.delete(erpExpInc);
return renderResult(Global.TRUE, text("删除收支信息成功!"));
}
@RequestMapping(value = "listAll")
@ResponseBody
public List<ErpExpInc> listAll(ErpExpInc erpExpInc) {
erpExpInc.setStatDate_gte(vDate.calculateStartCycleCode(erpExpInc.getCycleType()));
return erpExpIncService.findList(erpExpInc);
}
}

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.ErpExpIncDao">
<!-- 查询数据
<select id="findList" resultType="ErpExpInc">
SELECT ${sqlMap.column.toSql()}
FROM ${sqlMap.table.toSql()}
<where>
${sqlMap.where.toSql()}
</where>
ORDER BY ${sqlMap.order.toSql()}
</select> -->
</mapper>

View File

@@ -1,295 +0,0 @@
<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>

View File

@@ -1,11 +1,20 @@
<template>
<Card title="业务数据柱线图" style="width: 100%; height: 400px; margin: 20px 0;">
<Card title="交易收支柱线图" style="width: 100%; height: 400px; margin: 4px 0;">
<template #extra>
<BasicForm
:labelWidth="100"
:schemas="schemas"
:initialValues="defaultFormValues"
@submit="handleFormSubmit"
style="width: 400px;"
/>
</template>
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
</Card>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { BasicForm, FormSchema } from '@jeesite/core/components/Form';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
@@ -13,23 +22,72 @@ import { ErpSummaryAll, erpSummaryAllListAll } from '@jeesite/erp/api/erp/summar
import * as echarts from 'echarts';
const defaultFormValues = {
ctype: '1',
fcycle: 'D'
};
const currentFormValues = ref<Record<string, any>>({ ...defaultFormValues });
// 表单配置
const schemas: FormSchema[] = [
{
label: '交易类型',
field: 'ctype',
component: 'Select',
defaultValue: defaultFormValues.ctype,
componentProps: {
dictType: 'transaction_type',
allowClear: true,
onChange: (value: string) => {
currentFormValues.value.ctype = value;
fetchList(currentFormValues.value);
}
},
colProps: { md: 12, lg: 12 },
},
{
label: '周期类型',
field: 'fcycle',
component: 'Select',
defaultValue: defaultFormValues.fcycle,
componentProps: {
dictType: 'report_cycle',
allowClear: true,
onChange: (value: string) => {
currentFormValues.value.fcycle = value;
fetchList(currentFormValues.value);
}
},
colProps: { md: 12, lg: 12 },
},
];
const listSummary = ref<ErpSummaryAll[]>([]);
const chartDom = ref<HTMLDivElement | null>(null);
let myChart: echarts.ECharts | null = null;
const { createMessage } = useMessage();
const barLabelConfig = {
show: true, // 开启数值显示
position: 'top', // 数值显示在柱子顶部
distance: 3, // 数值与柱子顶部的间距px
textStyle: {
fontSize: 11, // 数值字体大小
color: '#333', // 数值字体颜色(深色更清晰)
fontWeight: '500' // 字体加粗
},
formatter: '{c} 元'
// 保留2位小数的工具函数
const formatNumber = (num: number | undefined, decimal = 2): number => {
if (num === undefined || isNaN(num)) return 0;
return Number(num.toFixed(decimal));
};
// 折线图标签配置
// 柱状图标签配置保留2位小数
const barLabelConfig = {
show: true,
position: 'top',
distance: 3,
textStyle: {
fontSize: 11,
color: '#333',
fontWeight: '500'
},
formatter: (params: any) => `${formatNumber(params.value).toFixed(2)}`
};
// 折线图标签配置保留2位小数
const lineLabelConfig = {
show: true,
position: 'top',
@@ -39,95 +97,227 @@ const lineLabelConfig = {
color: '#f5222d',
fontWeight: '500'
},
formatter: '{c} %'
formatter: (params: any) => `${formatNumber(params.value).toFixed(2)} %`
};
const fetchList = async () => {
const handleFormSubmit = (values: Record<string, any>) => {
currentFormValues.value = { ...values };
fetchList(currentFormValues.value);
};
// 获取数据列表
const fetchList = async (params: Record<string, any>) => {
try {
const params = { fcycle: 'D' , ctype: '1' }
const result = await erpSummaryAllListAll(params);
listSummary.value = result || [];
listSummary.value = result || [];
initChart();
} catch (error) {
console.error('获取数据列表失败:', error);
listSummary.value = [];
listSummary.value = [];
createMessage.error('数据加载失败,请稍后重试');
}
};
// 计算Y轴极值适配负数显示并确保0点对齐
const calculateYAxisExtent = (data: number[], isRate = false) => {
if (data.length === 0) return isRate ? [-10, 10] : [0, 100];
// 格式化数据保留2位小数
const formattedData = data.map(num => formatNumber(num));
const min = Math.min(...formattedData);
const max = Math.max(...formattedData);
// 计算内边距(百分比轴内边距更大,确保负数显示清晰)
const padding = isRate ? Math.max(Math.abs(max) * 0.2, Math.abs(min) * 0.2, 5)
: (max - min) * 0.1 || 10;
let minExtent = min - padding;
let maxExtent = max + padding;
// 强制包含0点确保X轴对齐
if (minExtent > 0) minExtent = 0;
if (maxExtent < 0) maxExtent = 0;
// 百分比轴确保正负对称(可选,提升视觉效果)
if (isRate) {
const absMax = Math.max(Math.abs(minExtent), Math.abs(maxExtent));
minExtent = -absMax;
maxExtent = absMax;
}
return [minExtent, maxExtent];
};
// 初始化图表
const initChart = () => {
if (!chartDom.value) return;
// 如果图表实例已存在,先销毁
if (myChart) {
myChart.dispose();
}
// 创建新的图表实例
myChart = echarts.init(chartDom.value);
// 处理空数据
const xAxisData = listSummary.value.length
? listSummary.value.map(item => item.cdate || '')
: [];
// 提取金额数据用于计算Y轴范围保留2位小数
const thisValueData = listSummary.value.map(item => formatNumber(item.thisValue));
const prevValueData = listSummary.value.map(item => formatNumber(item.prevValue));
const amountData = [...thisValueData, ...prevValueData];
const [amountMin, amountMax] = calculateYAxisExtent(amountData);
// 提取环比数据用于计算百分比轴范围保留2位小数
const rateData = listSummary.value.map(item => formatNumber(item.momRate));
const [rateMin, rateMax] = calculateYAxisExtent(rateData, true);
const option = {
title: {
left: 'center',
textStyle: { fontSize: 18, color: '#333' }
title: {
left: 'center',
textStyle: { fontSize: 18, color: '#333' }
},
tooltip: {
trigger: 'axis',
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontSize: 12 }
textStyle: { fontSize: 12 },
formatter: (params: any[]) => {
let res = params[0].axisValue;
params.forEach((param) => {
const value = formatNumber(param.value).toFixed(2);
const unit = param.seriesName === '环比' ? ' %' : ' 元';
res += `<br/>${param.marker}${param.seriesName}${value}${unit}`;
});
return res;
}
},
legend: {
data: ['本期金额', '上期金额', '环比'],
legend: {
data: ['本期金额', '上期金额', '环比'],
top: 10,
textStyle: { fontSize: 12 }
},
grid: {
left: '8%',
right: '8%',
bottom: '10%',
top: '20%',
containLabel: true
grid: {
left: '8%',
right: '12%', // 增加右侧边距适配百分比轴
bottom: '10%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
data: listSummary.value.map(item => (item.cdate)),
axisLabel: { fontSize: 12 }
xAxis: {
type: 'category',
data: xAxisData,
axisLabel: { fontSize: 12 },
// 确保X轴在0点位置
axisLine: {
onZero: true
}
},
yAxis: {
type: 'value',
name: '交易金额(元)',
axisLabel: { fontSize: 12 }
yAxis: [
{
type: 'value',
name: '交易金额(元)',
axisLabel: {
fontSize: 12,
formatter: (value: number) => formatNumber(value).toFixed(2)
},
min: amountMin,
max: amountMax,
axisLine: { onZero: true },
splitLine: {
lineStyle: { color: '#e8e8e8' }
},
axisTick: { alignWithLabel: true },
splitNumber: 4 // 控制刻度数量
},
{
type: 'value',
name: '环比(%',
axisLabel: {
fontSize: 12,
formatter: (value: number) => formatNumber(value).toFixed(2) + ' %'
},
min: rateMin,
max: rateMax,
position: 'right',
offset: 0,
splitLine: { show: false },
axisLine: { onZero: true },
axisTick: { alignWithLabel: true },
splitNumber: 4
}
],
// 空数据提示
noDataLoadingOption: {
text: '暂无数据',
textStyle: { fontSize: 14, color: '#999' },
effect: 'bubble',
effectOption: {
effect: {
n: 0
}
}
},
series: [
{
name: '本期金额',
type: 'bar',
data: listSummary.value.map(item => (item.thisValue)),
itemStyle: { color: '#1890ff' },
barWidth: 25, // 调整柱子宽度(数值越小越细)
label: barLabelConfig // 引用公共标签配置
{
name: '本期金额',
type: 'bar',
data: thisValueData,
itemStyle: {
color: '#1890ff',
borderRadius: [8, 8, 0, 0]
},
barWidth: 25,
barBorderRadius: [8, 8, 0, 0],
label: barLabelConfig,
yAxisIndex: 0
},
{
name: '上期金额',
type: 'bar',
data: listSummary.value.map(item => (item.prevValue)),
itemStyle: { color: '#52c41a' },
barWidth: 25,
label: barLabelConfig
{
name: '上期金额',
type: 'bar',
data: prevValueData,
itemStyle: {
color: '#52c41a',
borderRadius: [8, 8, 0, 0]
},
barWidth: 25,
barBorderRadius: [8, 8, 0, 0],
label: barLabelConfig,
yAxisIndex: 0
},
{
name: '环比',
type: 'line',
data: listSummary.value.map(item => (item.momRate)), // 三个月产品销售额的平均值
smooth: true, // 开启平滑曲线
symbol: 'circle', // 拐点样式为圆形
symbolSize: 8, // 拐点大小
data: rateData,
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: {
width: 3, // 线条宽度
color: '#f5222d' // 线条颜色
width: 3,
color: '#f5222d'
},
itemStyle: {
color: '#f5222d', // 拐点填充色
borderColor: '#fff', // 拐点边框色
borderWidth: 2 // 拐点边框宽度
color: '#f5222d',
borderColor: '#fff',
borderWidth: 2
},
label: lineLabelConfig, // 折线数值标签
label: lineLabelConfig,
emphasis: {
// 高亮状态下的样式
itemStyle: {
symbolSize: 12
}
},
yAxisIndex: 1,
// 0%基准线精确对齐X轴
markLine: {
silent: true,
lineStyle: { color: '#ccc', type: 'dashed', width: 1 },
data: [{
yAxis: 0,
lineStyle: { color: '#999', type: 'dashed' }
}]
}
}
]
@@ -136,34 +326,86 @@ const initChart = () => {
myChart.setOption(option);
};
const resizeChart = () => myChart?.resize();
// 调整图表大小
const resizeChart = () => {
if (myChart) {
myChart.resize({
animation: {
duration: 300,
easing: 'quadraticInOut'
}
});
}
};
// 监听窗口大小变化(防抖处理)
let resizeTimer: number;
const debounceResize = () => {
clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(resizeChart, 100);
};
// 生命周期
onMounted(async () => {
await fetchList()
initChart();
window.addEventListener('resize', resizeChart);
await fetchList(currentFormValues.value);
window.addEventListener('resize', debounceResize);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeChart);
myChart?.dispose(); // 销毁图表,避免内存泄漏
myChart = null;
window.removeEventListener('resize', debounceResize);
if (myChart) {
myChart.dispose();
myChart = null;
}
});
// 监听数据变化自动更新图表
watch(listSummary, () => {
if (myChart) {
initChart();
}
}, { deep: true });
</script>
<style scoped>
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
/* 可选:优化数值标签的样式(如果需要) */
:deep(.ant-card:hover) {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
/* 优化图表样式 */
:deep(.echarts-label) {
font-family: 'Microsoft YaHei', sans-serif;
}
/* 优化折线图tooltip样式 */
:deep(.echarts-tooltip) {
border-radius: 4px;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* 优化表单样式 */
:deep(.ant-form-item) {
margin-bottom: 8px;
}
:deep(.ant-select) {
width: 100%;
}
/* 优化双Y轴标签样式 */
:deep(.echarts-yaxis-label) {
font-size: 12px;
color: #666;
}
/* 优化0%基准线样式 */
:deep(.echarts-mark-line) {
z-index: 1;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<Card title="银行账户余额占比" style="width: 100%; height: 400px; margin: 20px 0;">
<Card title="银行账户余额占比" style="width: 100%; height: 400px; margin: 4px 0;">
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
</Card>
</template>

View File

@@ -1,113 +1,446 @@
<template>
<!-- 调整卡片和图表容器尺寸让图表整体更小 -->
<a-card title="业务数据柱状图" style="width: 100%; height: 400px; margin: 20px 0;">
<Card title="银行收支汇总图" style="width: 100%; height: 400px; margin: 4px 0;">
<template #extra>
<BasicForm
:labelWidth="100"
:schemas="schemas"
:initialValues="defaultFormValues"
@submit="handleFormSubmit"
style="width: 200px;"
/>
</template>
<div ref="chartDom" style="width: 100%; height: 300px;"></div>
</a-card>
</Card>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { BasicForm, FormSchema } from '@jeesite/core/components/Form';
import { erpExpIncListAll, ErpExpInc } from '@jeesite/erp/api/erp/expInc';
import * as echarts from 'echarts';
import type {
ECharts,
EChartsOption,
SeriesOption,
BarSeriesOption
} from 'echarts';
const chartDom = ref<HTMLDivElement | null>(null);
let myChart: echarts.ECharts | null = null;
// 表单默认值
const defaultFormValues = ref({
cycleType: 'D'
});
// 封装公共的标签配置(避免重复代码)
const barLabelConfig = {
show: true, // 开启数值显示
position: 'top', // 数值显示在柱子顶部
distance: 3, // 数值与柱子顶部的间距px
textStyle: {
fontSize: 11, // 数值字体大小
color: '#333', // 数值字体颜色(深色更清晰)
fontWeight: '500' // 字体加粗
// 表单配置
const schemas: FormSchema[] = [
{
label: '周期类型',
field: 'cycleType',
defaultValue: defaultFormValues.value.cycleType,
component: 'Select',
componentProps: {
dictType: 'report_cycle',
allowClear: true,
onChange: (value: string) => {
defaultFormValues.value.cycleType = value;
fetchList({ cycleType: value });
}
},
colProps: { md: 24, lg: 24 },
},
formatter: '{c} 万'
];
// 图表DOM引用
const chartDom = ref<HTMLDivElement | null>(null);
// 图表实例
let myChart: ECharts | null = null;
// 数据列表
const tableData = ref<ErpExpInc[]>([]);
// 加载状态
const loading = ref(false);
/**
* 单位转换元转万元除以10000
*/
const convertToTenThousand = (num: number): number => {
if (isNaN(num) || num === null || num === undefined) return 0;
return num / 10000;
};
const initChart = () => {
if (!chartDom.value) return;
myChart = echarts.init(chartDom.value);
/**
* 单位转换万元转元乘以10000- 用于Tooltip显示
*/
const convertToYuan = (num: number): number => {
if (isNaN(num) || num === null || num === undefined) return 0;
return num * 10000;
};
/**
* 获取数据列表 - 增加类型约束和加载状态
*/
const fetchList = async (params: { cycleType?: string }) => {
if (loading.value) return;
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: [30, 45, 55, 70, 85, 95],
itemStyle: { color: '#f5a623' }, // 给产品C改个不同颜色避免和B混淆
barWidth: 25,
label: barLabelConfig
try {
loading.value = true;
const cycleType = params.cycleType || defaultFormValues.value.cycleType;
const result = await erpExpIncListAll({ cycleType });
tableData.value = result || [];
initChart();
} catch (error) {
console.error('获取数据列表失败:', error);
tableData.value = [];
if (myChart) {
myChart.clear();
showEmptyChart();
}
} finally {
loading.value = false;
}
};
/**
* 工具函数保留2位小数处理0值和整数统一格式
*/
const formatNumber = (num: number): string => {
if (isNaN(num) || num === null || num === undefined) return '0.00';
return num.toFixed(2);
};
/**
* 基础柱状图配置 - 增加圆角效果
*/
const baseBarConfig: BarSeriesOption = {
type: 'bar',
barWidth: 25, // 加宽柱子,适配总值显示
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#333',
formatter: function(params: any) {
// 多层校验,确保数值安全
let value = 0;
if (params && params.value !== undefined && params.value !== null) {
value = typeof params.value === 'number' ? params.value : Number(params.value);
}
]
// 只有正数才显示标签,显示万元数值
return value > 0 ? formatNumber(value) : '';
}
},
itemStyle: {
borderWidth: 0,
borderType: 'solid',
// 圆角配置四个角都设置圆角数值越大越圆润建议值8-15根据barWidth调整
borderRadius: [8, 8, 0, 0], // 上左、上右、下右、下左(只给顶部圆角,更符合视觉习惯)
// 可选:添加轻微阴影增强立体感
shadowBlur: 3,
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowOffsetY: 2
},
legendHoverLink: false,
animationDuration: 500,
animationEasing: 'cubicOut'
};
/**
* 显示空数据图表
*/
const showEmptyChart = () => {
if (!myChart) return;
const emptyOption: EChartsOption = {
title: {
text: '暂无数据',
left: 'center',
top: 'middle',
textStyle: { fontSize: 16, color: '#999' }
},
xAxis: { type: 'category', data: [] },
yAxis: { type: 'value', name: '金额(万元)' },
series: [],
tooltip: { trigger: 'none' }
};
myChart.setOption(emptyOption);
};
/**
* 处理数据表生成ECharts所需结构改为总值模式
*/
const processTableData = () => {
// 过滤有效数据
const validData = tableData.value.filter(item =>
item?.statDate && (item.incomeAmount || item.expenseAmount)
);
// 1. 提取日期并排序升序24年在前25年在后
const dateSet = new Set(validData.map(item => item.statDate!));
const dateList = Array.from(dateSet).filter(Boolean).sort((a, b) => a.localeCompare(b));
// 2. 按日期汇总收支总值
const incomeTotalMap: Record<string, number> = {};
const expenseTotalMap: Record<string, number> = {};
const accountDetailMap: Record<string, Record<string, { income: number; expense: number }>> = {};
// 初始化映射
dateList.forEach(date => {
incomeTotalMap[date] = 0;
expenseTotalMap[date] = 0;
accountDetailMap[date] = {};
});
// 汇总数据
validData.forEach(item => {
const statDate = item.statDate!;
const accountName = item.accountName || '未知账户';
const incomeAmount = convertToTenThousand(Number(item.incomeAmount) || 0);
const expenseAmount = convertToTenThousand(Number(item.expenseAmount) || 0);
// 累加总值
incomeTotalMap[statDate] += incomeAmount;
expenseTotalMap[statDate] += expenseAmount;
// 保存账户明细
if (!accountDetailMap[statDate][accountName]) {
accountDetailMap[statDate][accountName] = { income: 0, expense: 0 };
}
accountDetailMap[statDate][accountName].income += incomeAmount;
accountDetailMap[statDate][accountName].expense += expenseAmount;
});
// 3. 生成系列数据(收入和支出两个系列)
const incomeSeries: BarSeriesOption = {
...baseBarConfig,
name: '收入',
data: dateList.map(date => incomeTotalMap[date]),
itemStyle: {
...baseBarConfig.itemStyle,
color: '#52c41a', // 收入绿色
// 可选:收入柱子添加渐变效果,增强视觉体验
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#73d13d' },
{ offset: 1, color: '#52c41a' }
])
},
label: {
...baseBarConfig.label,
color: '#52c41a'
}
};
myChart.setOption(option);
const expenseSeries: BarSeriesOption = {
...baseBarConfig,
name: '支出',
data: dateList.map(date => expenseTotalMap[date]),
itemStyle: {
...baseBarConfig.itemStyle,
color: '#f5222d', // 支出红色
// 可选:支出柱子添加渐变效果,增强视觉体验
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#ff4d4f' },
{ offset: 1, color: '#f5222d' }
])
},
label: {
...baseBarConfig.label,
color: '#f5222d'
}
};
const series: SeriesOption[] = [incomeSeries, expenseSeries];
return {
dateList,
series,
accountDetailMap,
incomeTotalMap,
expenseTotalMap
};
};
// 窗口缩放时自适应图表
const resizeChart = () => myChart?.resize();
/**
* 初始化图表改为总值显示恢复原Tooltip格式
*/
const initChart = () => {
if (!chartDom.value) return;
// 销毁旧实例
if (myChart) {
myChart.dispose();
myChart = null;
}
try {
// 创建新实例
myChart = echarts.init(chartDom.value);
const { dateList, series, accountDetailMap, incomeTotalMap, expenseTotalMap } = processTableData();
// 空数据处理
if (dateList.length === 0) {
showEmptyChart();
return;
}
const option: EChartsOption = {
title: {
left: 'center',
textStyle: { fontSize: 16, color: '#333' }
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontSize: 12 },
padding: [10, 15],
formatter: function(params: any) {
if (!params || params.length === 0) return '';
const date = params[0]?.axisValue || '';
// Tooltip中转换回元单位计算
const incomeTotal = convertToYuan(incomeTotalMap[date] || 0);
const expenseTotal = convertToYuan(expenseTotalMap[date] || 0);
const netIncome = incomeTotal - expenseTotal;
let accountRows = '';
const accountDetails = accountDetailMap[date] || {};
const accountNames = Object.keys(accountDetails).sort();
// 生成账户明细行(保持原格式)
accountNames.forEach(account => {
const detail = accountDetails[account];
if (detail.income > 0 || detail.expense > 0) {
// 转换回元单位显示
const incomeValue = convertToYuan(detail.income);
const expenseValue = convertToYuan(detail.expense);
accountRows += `
<div style="display: flex; justify-content: space-between; margin: 4px 0;">
<span style="display: flex; align-items: center;">
<span style="display: inline-block; width: 8px; height: 8px; background: ${detail.income > 0 ? '#52c41a' : '#f5222d'}; border-radius: 2px; margin-right: 6px;"></span>
${account}
</span>
<span style="display: flex; gap: 15px;">
<span style="color: #52c41a;">收入:${formatNumber(incomeValue)} </span>
<span style="color: #f5222d;">支出:${formatNumber(expenseValue)} </span>
</span>
</div>
`;
}
});
// 保持原Tooltip的HTML结构
return `
<div style="font-weight: 600; margin-bottom: 8px;">${date}</div>
${accountRows}
<div style="border-top: 1px solid #eee; margin: 8px 0; padding-top: 8px; display: flex; justify-content: space-between; font-weight: 600;">
<span style="display: flex; gap: 15px;">
<span style="color: #52c41a;">总收入:${formatNumber(incomeTotal)} 元</span>
<span style="color: #f5222d;">总支出:${formatNumber(expenseTotal)} 元</span>
</span>
</div>
<div style="text-align: left; color: #333; font-weight: 600;">
净收入:${formatNumber(netIncome)}
</div>
`;
}
},
legend: {
data: ['收入', '支出'],
top: 10,
left: 'center',
orient: 'horizontal',
textStyle: { fontSize: 12, color: '#333' },
itemWidth: 12,
itemHeight: 12,
itemGap: 20
},
grid: {
left: '5%',
right: '5%',
bottom: '15%',
top: '20%',
containLabel: true,
padding: [10, 0, 0, 0]
},
xAxis: {
type: 'category',
data: dateList,
axisLabel: {
fontSize: 12,
rotate: 30,
interval: 0
},
axisTick: { inside: true },
axisLine: { onZero: true }
},
yAxis: {
type: 'value',
name: '金额(万元)',
min: 0,
axisLabel: {
fontSize: 12,
formatter: (value: any) => `${formatNumber(Number(value))} 万元`
},
splitLine: { lineStyle: { color: '#f0f0f0' } },
axisTick: { show: false },
axisLine: { show: false },
max: (value: { max: number }) => value.max > 0 ? value.max * 1.15 : 0.1
},
series: series,
responsive: true,
// 取消堆叠模式,改为分组显示
barGap: '20%',
barCategoryGap: '30%'
};
myChart.setOption(option);
} catch (error) {
console.error('初始化图表失败:', error);
showEmptyChart();
}
};
/**
* 处理表单提交
*/
const handleFormSubmit = (values: { cycleType?: string }) => {
if (values.cycleType) {
fetchList({ cycleType: values.cycleType });
}
};
/**
* 窗口缩放自适应
*/
const resizeChart = () => {
if (myChart) {
clearTimeout((window as any).chartResizeTimer);
(window as any).chartResizeTimer = setTimeout(() => {
myChart?.resize();
}, 100);
}
};
// 监听数据变化自动更新图表
watch(tableData, () => {
if (!loading.value) {
initChart();
}
}, { deep: true });
// 生命周期
onMounted(() => {
initChart();
fetchList({});
window.addEventListener('resize', resizeChart);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeChart);
myChart?.dispose(); // 销毁图表,避免内存泄漏
myChart = null;
if (myChart) {
myChart.dispose();
myChart = null;
}
clearTimeout((window as any).chartResizeTimer);
});
</script>
@@ -117,8 +450,38 @@ onUnmounted(() => {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 可选:优化数值标签的样式(如果需要) */
:deep(.echarts-label) {
font-family: 'Microsoft YaHei', sans-serif;
/* 图例颜色 */
:deep(.echarts-legend-item:nth-child(1) .echarts-legend-symbol) {
background-color: #52c41a !important;
border-radius: 4px; /* 图例也添加圆角,保持风格统一 */
}
:deep(.echarts-legend-item:nth-child(2) .echarts-legend-symbol) {
background-color: #f5222d !important;
border-radius: 4px; /* 图例也添加圆角,保持风格统一 */
}
/* Tooltip样式 */
:deep(.echarts-tooltip) {
border-radius: 8px;
padding: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
border: none;
background: #fff;
z-index: 9999 !important;
}
/* 优化标签显示效果 */
:deep(.echarts-bar-label) {
font-weight: 500;
text-shadow: 0 1px 1px rgba(255,255,255,0.8);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 优化x轴标签显示 */
:deep(.echarts-xaxis-label) {
word-break: keep-all;
white-space: nowrap;
}
</style>

View File

@@ -1,16 +1,10 @@
<template>
<!-- 主容器 -->
<div class="main-container">
<!-- 两列布局限制高度 + 垂直间距 -->
<div class="dashboard-container">
<div class="two-column-layout">
<div class="column left-column">
<ChartPie />
</div>
<div class="column right-column"></div>
</div>
<div class="chart-line-wrapper">
<ChartLine />
<ChartPie />
<ChartBar />
</div>
<ChartLine class="chart-line-wrapper" />
</div>
</template>
@@ -18,87 +12,35 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { Card } from 'ant-design-vue';
import ChartBar from './components/ChartBar.vue';
import ChartPie from './components/ChartPie.vue';
import ChartLine from './components/ChartLine.vue';
</script>
<style scoped lang="less">
.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;
<style scoped>
.dashboard-container {
padding: 0 8px;
}
// 左右两列布局:限制高度 + 底部间距
.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 {
gap: 12px;
width: 100%;
height: 100%;
margin: 0;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
padding: 0;
}
.column-content {
flex: 1;
padding: 4px 0;
overflow: auto;
.chart-line-wrapper {
width: 100%;
margin-top: 0;
padding: 0;
}
/* 响应式适配(可选) */
@media (max-width: 768px) {
.two-column-layout {
flex-direction: column;
gap: 8px;
margin-bottom: 4px; /* 移动端也保持紧凑 */
}
}
// 长内容样式(测试用)
.long-content {
padding: 10px 0;
}
</style>
</style>

View File

@@ -2,7 +2,7 @@
<div class="pt-2 lg:flex">
<Avatar :src="userinfo.avatarUrl || headerImg" :size="72" class="!mx-auto !block" />
<div class="mt-2 flex flex-col justify-center md:ml-6 md:mt-0">
<h1 class="text-md md:text-lg">您好, <a @click="handleMyWorkClick">{{ userinfo.userName }}</a>, 开始您一天的工作吧</h1>
<h1 class="text-md md:text-lg">您好, {{ userinfo.userName }}, 开始您一天的工作吧</h1>
<span class="text-secondary"> 今日晴20 - 32 </span>
</div>
<div class="mt-4 flex flex-1 justify-end md:mt-0">
@@ -32,8 +32,4 @@
const router = useRouter();
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
const handleMyWorkClick = () => {
router.push('/desktop/workbench');
};
</script>

View File

@@ -3,17 +3,22 @@
<template #headerContent>
<MySchedule />
</template>
<div>
ssssss
<Tabs v-model:activeKey="activeKey">
<TabPane key="1" tab="日程管理"></TabPane>
<TabPane key="2" tab="工单管理"></TabPane>
<TabPane key="3" tab="字典管理"></TabPane>
</Tabs>
</div>
</PageWrapper>
</template>
<script lang="ts" setup name="AboutPage">
import { h } from 'vue';
import { Tag } from 'ant-design-vue';
import { h, ref } from 'vue';
import { Tag, Tabs ,TabPane } from 'ant-design-vue';
import { PageWrapper } from '@jeesite/core/components/Page';
import MySchedule from './components/MySchedule.vue';
const activeKey = ref('1');
</script>

View File

@@ -0,0 +1,37 @@
/**
* 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';
const { adminPath } = useGlobSetting();
export interface ErpExpInc extends BasicModel<ErpExpInc> {
createTime?: string; // 记录时间
accountName?: string; // account_name
statDate?: string; // stat_date
cycleType?: string; // cycle_type
incomeAmount?: number; // income_amount
expenseAmount?: number; // expense_amount
}
export const erpExpIncList = (params?: ErpExpInc | any) =>
defHttp.get<ErpExpInc>({ url: adminPath + '/erp/expInc/list', params });
export const erpExpIncListAll = (params?: ErpExpInc | any) =>
defHttp.get<ErpExpInc[]>({ url: adminPath + '/erp/expInc/listAll', params });
export const erpExpIncListData = (params?: ErpExpInc | any) =>
defHttp.post<Page<ErpExpInc>>({ url: adminPath + '/erp/expInc/listData', params });
export const erpExpIncForm = (params?: ErpExpInc | any) =>
defHttp.get<ErpExpInc>({ url: adminPath + '/erp/expInc/form', params });
export const erpExpIncSave = (params?: any, data?: ErpExpInc | any) =>
defHttp.postJson<ErpExpInc>({ url: adminPath + '/erp/expInc/save', params, data });
export const erpExpIncDelete = (params?: ErpExpInc | any) =>
defHttp.get<ErpExpInc>({ url: adminPath + '/erp/expInc/delete', params });