| | |
| | | <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> |
| | | 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> |