新增前端vue
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
37
web-vue/packages/erp/api/erp/expInc.ts
Normal file
37
web-vue/packages/erp/api/erp/expInc.ts
Normal 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 });
|
||||
Reference in New Issue
Block a user