spring
2025-08-08 9cc769fcd5e7efbe363cdaabead04824bf9d7812
Merge remote-tracking branch 'origin/dev_7004' into dev_ai
已修改5个文件
已添加13个文件
563 ■■■■■ 文件已修改
src/api/financialManagement/financialStatements.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/DIN Alternate Bold.ttf 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/font.css 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/3.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/4.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/5.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/支出.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/支出金额.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/收入列帐.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/收入收款.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/png/收入金额.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Echarts/echarts.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialStatements/index.vue 511 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/revenueManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/financialStatements.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
import request from "@/utils/request";
// æ ¹æ®æ—¥æœŸæŸ¥è¯¢
export const reportForms = (params) => {
  console.log(params);
  return request({
    url: "/account/accountExpense/report/forms",
    method: "get",
    params,
  });
};
// æŸ¥è¯¢æ¯æœˆæ•°æ®-收入
export const reportIncome = (params) => {
  console.log(params);
  return request({
    url: "/account/accountExpense/report/income",
    method: "get",
    params,
  });
};
// æŸ¥è¯¢æ¯æœˆæ•°æ®-支出
export const reportExpense = (params) => {
  console.log(params);
  return request({
    url: "/account/accountExpense/report/expense",
    method: "get",
    params,
  });
};
src/assets/fonts/DIN Alternate Bold.ttf
Binary files differ
src/assets/fonts/font.css
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
@font-face {
  font-family: "MyCustomFont";
  src: url("./DIN Alternate Bold.ttf") format("truetype");
  font-weight: 700; /* ç²—体 */
  font-style: normal;
  font-display: swap;
}
src/assets/icons/png/1.png
src/assets/icons/png/2.png
src/assets/icons/png/3.png
src/assets/icons/png/4.png
src/assets/icons/png/5.png
src/assets/icons/png/Ö§³ö.png
src/assets/icons/png/Ö§³ö½ð¶î.png
src/assets/icons/png/ÊÕÈëÁÐÕÊ.png
src/assets/icons/png/ÊÕÈëÊÕ¿î.png
src/assets/icons/png/ÊÕÈë½ð¶î.png
src/components/Echarts/echarts.vue
@@ -1,6 +1,7 @@
<template>
  <div>
  <div style="position: relative;">
    <div ref="chartRef" :style="chartStyle"></div>
    <slot></slot>
  </div>
</template>
@@ -89,7 +90,7 @@
// Methods
function generateChart(option) {
  const copiedOption = JSON.parse(JSON.stringify(option)) // âœ… æ·±æ‹·è´
  const copiedOption = option
  
  if (copiedOption.series && copiedOption.series.length > 0) {
    copiedOption.series.forEach((s, index) => {
@@ -118,7 +119,7 @@
    series: props.series,
    grid: props.grid,
    legend: props.legend,
    tooltip: props.tooltip
    tooltip: props.tooltip,
  }
  
  chartInstance.clear()
@@ -147,7 +148,7 @@
// Watch all reactive props that affect the chart
watch(
    () => [props.xAxis, props.series],
    () => [props.xAxis, props.series, props.legend, props.tooltip],
    () => {
      if (chartInstance) {
        renderChart()
src/main.js
@@ -22,6 +22,7 @@
import "virtual:svg-icons-register";
import SvgIcon from "@/components/SvgIcon";
import elementIcons from "@/components/SvgIcon/svgicon";
import "./assets/fonts/font.css";
import "./permission"; // permission control
src/views/financialManagement/expenseManagement/index.vue
@@ -69,7 +69,7 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia" @close="handleQuery"></files-dia>
    <files-dia ref="filesDia"></files-dia>
  </div>
</template>
src/views/financialManagement/financialStatements/index.vue
@@ -1,4 +1,513 @@
<template>
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œæ—¥æœŸç­›é€‰ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        format="YYYY-MM-DD"
        value-format="YYYY-MM-DD"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        :default-value="[new Date(firstDayOfMonth), new Date()]"
        @change="handleDateChange"
        class="w-full md:w-auto"
        style="margin-right: 30px;"
      />
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetDateRange"
        size="default"
      >
        é‡ç½®
      </el-button>
    </div>
    <main class="container mx-auto px-4 pb-10">
      <!-- è´¢åŠ¡æŒ‡æ ‡å¡ç‰‡ -->
      <div class="grid-container">
        <!-- æ€»æ”¶å…¥ -->
        <el-card class="bg1">
          <p>总收入</p>
          <h3>
            Â¥{{ pageInfo.totalIncome }}
          </h3>
        </el-card>
        <!-- æ”¶å…¥ç¬”æ•° -->
        <el-card class="bg2">
          <p>收入笔数</p>
          <h3>
            {{ pageInfo.incomeNumber }}
          </h3>
        </el-card>
        <!-- æ€»æ”¯å‡º -->
        <el-card class="bg3">
          <p>总支出</p>
          <h3>
            Â¥{{ pageInfo.totalExpense }}
          </h3>
        </el-card>
        <!-- æ”¯å‡ºç¬”æ•° -->
        <el-card class="bg4">
          <p>支出笔数</p>
          <h3>
            {{ pageInfo.expenseNumber }}
          </h3>
        </el-card>
        <!-- å‡€æ”¶å…¥ -->
        <el-card class="bg5">
          <p>净收入</p>
          <h3>
            Â¥{{ pageInfo.netRevenue }}
          </h3>
        </el-card>
      </div>
      <!-- æ”¶å…¥ç»Ÿè®¡å›¾è¡¨ -->
      <div class="grid-layout">
        <el-card style="margin-bottom: 20px;">
          <h2 class="section-title">收入统计(元)</h2>
          <div class="echarts">
            <Echarts :legend="pieLegend0" :chartStyle="chartStylePie"
                                         :series="materialPieSeries0"
                                         :tooltip="pieTooltip" style="height: 260px;width: 35%;">
                     <div class="chart-num">
                      <span style="font-size: 22px;">收入</span>
                      <span style="font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalIncome }}</span>
                     </div>
                    </Echarts>
            <Echarts ref="chart"
                                 :chartStyle="chartStyle"
                                 :grid="grid"
                                 :legend="lineLegend"
                                 :series="lineSeries0"
                                 :tooltip="tooltip"
                                 :xAxis="xAxis0"
                                 :yAxis="yAxis0"
                                 style="height: 260px;width: 64%;"></Echarts>
          </div>
        </el-card>
        <!-- æ”¯å‡ºç»Ÿè®¡å›¾è¡¨ -->
        <el-card>
          <h2 class="section-title">支出统计(元)</h2>
          <div class="echarts">
            <Echarts ref="chart"
                    :legend="pieLegend1"
                    :chartStyle="chartStylePie"
                                         :series="materialPieSeries1"
                                         :tooltip="pieTooltip"
                     style="height: 260px;width: 35%;">
                     <div class="chart-num">
                      <span style="font-size: 22px;">支出</span>
                      <span style="font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalExpense }}</span>
                     </div></Echarts>
            <Echarts ref="chart"
                                 :chartStyle="chartStyle"
                                 :grid="grid"
                                 :legend="lineLegend"
                                 :series="lineSeries1"
                                 :tooltip="tooltip"
                                 :xAxis="xAxis1"
                                 :yAxis="yAxis1"
                                 style="height: 260px;width: 64%;"></Echarts>
          </div>
        </el-card>
      </div>
    </main>
  </div>
</template>
<script setup>
</script>
import { ref, computed, onMounted, reactive } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
import dayjs from "dayjs";
// æ—¥æœŸèŒƒå›´
const dateRange = ref([]);
const firstDayOfMonth = ref(null);
const chartStyle = {
    width: '100%',
    height: '100%', // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
  position:'relative',
}
const grid = {
    left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
}
const lineLegend = {
    show: false,
}
// æŠ˜çº¿å›¾æç¤ºæ¡†
const tooltip = reactive({
  trigger: 'axis',
  axisPointer: {
    type: 'line',
    lineStyle: { color: '#aaa' }
  },
  // è‡ªå®šä¹‰å†…容
  formatter: function (params) {
    if (!params || !params.length) return ''
    const axisLabel = params[0].axisValueLabel || params[0].axisValue || ''
    const rows = params
      .map(p => {
        const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`
        return `${colorDot}${p.seriesName}: ${p.value}`
      })
      .join('<br/>')
    return `<div>${axisLabel}</div><div>${rows}</div>`
  }
})
const months = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
const lineSeries0 = ref([])
const lineSeries1 = ref([])
const xAxis0 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
  },
]);
const xAxis1 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
  },
]);
const yAxis0 = [
{
    type: 'value',
    name: '收入统计', // å·¦ä¾§yè½´
    position: 'left',
    min: 0,
    // åæ ‡è½´åç§°æ ·å¼
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
]
const yAxis1 = [
{
    type: 'value',
    name: '支出统计', // å·¦ä¾§yè½´
    position: 'left',
    min: 0,
    // åæ ‡è½´åç§°æ ·å¼
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
]
const chartStylePie = {
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const pieColors = ['#F04864','#FACC14', '#8543E0', '#1890FF', '#13C2C2','#2FC25B']; // å¯æ ¹æ®å®žé™…调整
const pieData0 = ref([]);
const pieData1 = ref([]);
const pieLegend0 = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData0.value.map(item => item.name),
  formatter: function(name) {
    const item = pieData0.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
const pieLegend1 = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData1.value.map(item => item.name),
  formatter: function(name) {
    const item = pieData1.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
const materialPieSeries0 = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: pieData0.value,
    color: pieColors
  }
]);
const materialPieSeries1 = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: pieData1.value,
    color: pieColors
  }
]);
const pieTooltip = reactive({
    trigger: 'item',
  formatter: function(params) {
    // æ£€æŸ¥æ•°æ®æ˜¯å¦å­˜åœ¨
    if (!params.data) return params.name;
    // æ‹¼æŽ¥å®Œæ•´å†…容
    return `
      <div>
        <div style="color:${params.color};font-size:16px;">●</div>
        <div>${params.name}</div>
        <div>占比:${params.data.percent}</div>
        <div>金额:${params.data.amount}</div>
      </div>
    `;
  }
})
const pageInfo = ref({
})
const getData = async () => {
  try {
    const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
    if(code === 200) {
      pageInfo.value = data
      pieData0.value = data.incomeType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
      }))
      pieData1.value = data.expenseType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
      }))
    }
  } catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportIncome();
    if(code==200){
      lineSeries0.value = data.map(item=>({
        name:item.typeName,
        type: 'line',
        data:item.account.map(item=>Number(item))
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportExpense();
    if(code==200){
      lineSeries1.value = data.map(item=>({
        name:item.typeName,
        type: 'line',
        data:item.account.map(item=>Number(item))
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
};
// åˆå§‹åŒ–日期范围(默认当月)
onMounted(() => {
  const today = new Date();
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
  firstDayOfMonth.value = firstDay;
  dateRange.value = [dayjs(firstDay).format("YYYY-MM-DD"), dayjs(today).format("YYYY-MM-DD")];
  getData()
});
// å¤„理日期范围变化
const handleDateChange = (newRange) => {
  if (newRange && newRange.length === 2) {
    dateRange.value = newRange;
    getData()
  }
};
// é‡ç½®æ—¥æœŸèŒƒå›´
const resetDateRange = () => {
  const today = new Date();
  const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
  dateRange.value = [dayjs(firstDay).format("YYYY-MM-DD"), dayjs(today).format("YYYY-MM-DD")];
  getData()
};
</script>
<style scoped lang="scss">
/* åŸºç¡€æ ·å¼è¡¥å…… */
:root {
  --el-color-primary: #4f46e5;
}
.el-card{
  position: relative;
  border-radius: 12px;
  padding: 14px 10px 10px 10px;
  box-shadow: 0 2px 8px #eee;
  :deep(.el-card__body){
    padding: 10px 20px !important;
  }
  &.bg1{
    background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
  }
  &.bg2{
    background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
  }
  &.bg3{
    background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
  }
  &.bg4{
    background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
  }
  &.bg5{
    background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
  }
}
.grid-container {
  /* grid å®¹å™¨åŸºç¡€æ ·å¼ */
  display: grid;
  gap: 1rem; /* gap-4 å¯¹åº” 1rem (16px) */
  margin-bottom: 2rem; /* mb-8 å¯¹åº” 2rem (32px) */
  p{
    font-size: 22px;
    margin-top: 0px;
    color: #fff;
  }
  h3{
    font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;
    margin: 10px 0;
    color: #fff;
  }
}
/* ç§»åŠ¨ç«¯é»˜è®¤æ ·å¼ (grid-cols-1) */
.grid-container {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
/* å°å±å¹•及以上 (sm:grid-cols-2) */
@media (min-width: 640px) {
  .grid-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* å¤§å±å¹•及以上 (lg:grid-cols-5) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
/* å¡ç‰‡æ‚¬åœæ•ˆæžœå¢žå¼º */
.el-card:hover {
  transform: translateY(-2px);
}
.echarts{
  display: flex;
  justify-content: space-between;
}
/* å›¾è¡¨å®¹å™¨æ ·å¼ */
.el-chart {
  width: 100%;
  height: 100%;
}
.section-title {
    position: relative;
    font-size: 18px;
    color: #333;
    padding-left: 10px;
    margin-bottom: 10px;
    font-weight: 700;
}
.section-title::before {
    position: absolute;
    left: 0;
    top: 0px;
    content: '';
    width: 4px;
    height: 18px;
    background-color: #002FA7;
    border-radius: 2px;
}
.chart-num{
  position: absolute;
  z-index: 3;
  top: 92px;
  left: 92px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
</style>
src/views/financialManagement/revenueManagement/index.vue
@@ -69,7 +69,7 @@
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia" @close="handleQuery"></files-dia>
    <files-dia ref="filesDia"></files-dia>
  </div>
</template>