| | |
| | | <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" |
| | | type="monthrange" |
| | | format="YYYY-MM" |
| | | value-format="YYYY-MM" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | clearable |
| | | start-placeholder="开始月份" |
| | | end-placeholder="结束月份" |
| | | :disabled-date="disabledDate" |
| | | @change="handleDateChange" |
| | | class="w-full md:w-auto" |
| | | style="margin-right: 30px;" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import { ref, computed, onMounted, reactive, nextTick, getCurrentInstance } from 'vue'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements"; |
| | |
| | | |
| | | // 日期范围 |
| | | const dateRange = ref(null); |
| | | const { proxy } = getCurrentInstance(); |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', // 设置图表容器的高度 |
| | |
| | | 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([]) |
| | | |
| | | // 根据月份范围生成 x 轴数据 |
| | | const generateMonthLabels = (startMonth, endMonth) => { |
| | | const labels = []; |
| | | let current = dayjs(startMonth); |
| | | const end = dayjs(endMonth); |
| | | |
| | | while (current.isBefore(end) || current.isSame(end, 'month')) { |
| | | labels.push(`${current.month() + 1}月`); |
| | | current = current.add(1, 'month'); |
| | | } |
| | | |
| | | return labels; |
| | | }; |
| | | |
| | | const xAxis0 = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: true, alignWithLabel: true }, |
| | | data: months, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | const xAxis1 = ref([ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: true, alignWithLabel: true }, |
| | | data: months, |
| | | data: [], |
| | | }, |
| | | ]); |
| | | const yAxis0 = [ |
| | |
| | | left: '60%', |
| | | orient: 'vertical', |
| | | icon: 'circle', |
| | | data: pieData0.value.map(item => item.name), |
| | | data: (pieData0.value || []).filter(item => item && item.name).map(item => item.name), |
| | | formatter: function(name) { |
| | | const item = pieData0.value.find(i => i.name === name); |
| | | if (!name) return ''; |
| | | const item = pieData0.value.find(i => i && i.name === name); |
| | | if (!item) return name; |
| | | return `${name} | ${item.percent} ${item.amount}`; |
| | | }, |
| | |
| | | left: '60%', |
| | | orient: 'vertical', |
| | | icon: 'circle', |
| | | data: pieData1.value.map(item => item.name), |
| | | data: (pieData1.value || []).filter(item => item && item.name).map(item => item.name), |
| | | formatter: function(name) { |
| | | const item = pieData1.value.find(i => i.name === name); |
| | | if (!name) return ''; |
| | | const item = pieData1.value.find(i => i && i.name === name); |
| | | if (!item) return name; |
| | | return `${name} | ${item.percent} ${item.amount}`; |
| | | }, |
| | |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: pieData0.value, |
| | | data: (pieData0.value || []).filter(item => item && item.name), |
| | | color: pieColors |
| | | } |
| | | ]); |
| | |
| | | label: { |
| | | show: false |
| | | }, |
| | | data: pieData1.value, |
| | | data: (pieData1.value || []).filter(item => item && item.name), |
| | | color: pieColors |
| | | } |
| | | ]); |
| | |
| | | const pageInfo = ref({ |
| | | }) |
| | | |
| | | // 获取最近六个月的范围 |
| | | const getLastSixMonths = () => { |
| | | const endMonth = dayjs().format('YYYY-MM'); |
| | | const startMonth = dayjs().subtract(5, 'month').format('YYYY-MM'); |
| | | return [startMonth, endMonth]; |
| | | }; |
| | | |
| | | const getData = async () => { |
| | | if (!dateRange.value || !dateRange.value.length) { |
| | | if (!dateRange.value || !Array.isArray(dateRange.value) || dateRange.value.length !== 2) { |
| | | return; |
| | | } |
| | | const startDateStr = dateRange.value[0]; |
| | | const endDateStr = dateRange.value[1]; |
| | | if (!startDateStr || !endDateStr) { |
| | | return; |
| | | } |
| | | |
| | | // 验证日期格式并转换为完整日期 |
| | | const startDate = dayjs(startDateStr); |
| | | const endDate = dayjs(endDateStr); |
| | | if (!startDate.isValid() || !endDate.isValid()) { |
| | | console.error('无效的日期格式'); |
| | | return; |
| | | } |
| | | |
| | | // 更新 x 轴数据 |
| | | const monthLabels = generateMonthLabels(startDateStr, endDateStr); |
| | | xAxis0.value[0].data = monthLabels; |
| | | xAxis1.value[0].data = monthLabels; |
| | | |
| | | // 开始月份拼接第一天,结束月份拼接最后一天 |
| | | const entryDateStart = startDate.startOf('month').format('YYYY-MM-DD'); |
| | | const entryDateEnd = endDate.endOf('month').format('YYYY-MM-DD'); |
| | | |
| | | 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}` |
| | | const {code,data} = await reportForms({entryDateStart, entryDateEnd}); |
| | | if(code === 200 && data) { |
| | | pageInfo.value = data || {}; |
| | | // 安全处理数据,过滤掉 null 或 undefined |
| | | pieData0.value = (data.incomeType || []).filter(item => item && item.typeName).map(item=>({ |
| | | name:item.typeName || '', |
| | | value:item.account || 0, |
| | | percent:`${((item.proportion || 0) * 100).toFixed(2)}%`, |
| | | amount:`¥${(item.account || 0).toFixed(2)}` |
| | | })) |
| | | pieData1.value = data.expenseType.map(item=>({ |
| | | name:item.typeName, |
| | | value:item.account, |
| | | percent:`${item.proportion*100}%`, |
| | | amount:`¥${item.account}` |
| | | pieData1.value = (data.expenseType || []).filter(item => item && item.typeName).map(item=>({ |
| | | name:item.typeName || '', |
| | | value:item.account || 0, |
| | | percent:`${((item.proportion || 0) * 100).toFixed(2)}%`, |
| | | amount:`¥${(item.account || 0).toFixed(2)}` |
| | | })) |
| | | |
| | | } |
| | | } catch (error) { |
| | | console.error('获取财务指标数据失败:', error); |
| | | } |
| | | try{ |
| | | const {code,data} = await reportIncome(); |
| | | if(code==200){ |
| | | lineSeries0.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | const {code,data} = await reportIncome({entryDateStart, entryDateEnd}); |
| | | if(code==200 && data && Array.isArray(data)){ |
| | | lineSeries0.value = data.filter(item => item && item.typeName).map(item=>({ |
| | | name:item.typeName || '', |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(val => Number(val) || 0) |
| | | })) |
| | | |
| | | } |
| | | }catch (error) { |
| | | console.error('获取财务指标数据失败:', error); |
| | | } |
| | | try{ |
| | | const {code,data} = await reportExpense(); |
| | | if(code==200){ |
| | | lineSeries1.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | const {code,data} = await reportExpense({entryDateStart, entryDateEnd}); |
| | | if(code==200 && data && Array.isArray(data)){ |
| | | lineSeries1.value = data.filter(item => item && item.typeName).map(item=>({ |
| | | name:item.typeName || '', |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(val => Number(val) || 0) |
| | | })) |
| | | |
| | | } |
| | | }catch (error) { |
| | | console.error('获取财务指标数据失败:', error); |
| | |
| | | |
| | | // 初始化 |
| | | onMounted(() => { |
| | | // 不设置默认日期,由用户手动选择 |
| | | // 设置默认值为最近六个月 |
| | | const defaultRange = getLastSixMonths(); |
| | | dateRange.value = defaultRange; |
| | | // 使用 nextTick 确保组件完全渲染后再调用 |
| | | nextTick(() => { |
| | | getData(); |
| | | }); |
| | | }); |
| | | |
| | | // 处理日期范围变化 |
| | | const handleDateChange = (newRange) => { |
| | | dateRange.value = newRange; |
| | | if (newRange && newRange.length === 2) { |
| | | getData() |
| | | // 限制月份选择范围(最多12个月) |
| | | const disabledDate = (time) => { |
| | | // 如果没有选择开始月份,不禁用任何日期 |
| | | if (!dateRange.value || !Array.isArray(dateRange.value) || !dateRange.value[0]) { |
| | | return false; |
| | | } |
| | | |
| | | const startMonth = dayjs(dateRange.value[0]); |
| | | const currentMonth = dayjs(time); |
| | | |
| | | // 如果当前月份在开始月份之前,禁用 |
| | | if (currentMonth.isBefore(startMonth, 'month')) { |
| | | return true; |
| | | } |
| | | |
| | | // 计算最大允许的月份(开始月份 + 11个月 = 12个月) |
| | | const maxMonth = startMonth.add(11, 'month'); |
| | | |
| | | // 禁用超过12个月的月份 |
| | | return currentMonth.isAfter(maxMonth, 'month'); |
| | | }; |
| | | |
| | | // 重置日期范围 |
| | | // 处理月份范围变化 |
| | | const handleDateChange = (newRange) => { |
| | | if (!newRange || !Array.isArray(newRange) || newRange.length !== 2) { |
| | | return; |
| | | } |
| | | |
| | | // 验证月份范围不超过12个月 |
| | | const startDate = dayjs(newRange[0]); |
| | | const endDate = dayjs(newRange[1]); |
| | | const monthDiff = endDate.diff(startDate, 'month'); |
| | | |
| | | if (monthDiff > 11) { |
| | | proxy.$modal.msgWarning('最多只能选择12个月份'); |
| | | // 自动调整为12个月 |
| | | const adjustedEnd = startDate.add(11, 'month').format('YYYY-MM'); |
| | | dateRange.value = [newRange[0], adjustedEnd]; |
| | | getData(); |
| | | return; |
| | | } |
| | | |
| | | dateRange.value = newRange; |
| | | getData(); |
| | | }; |
| | | |
| | | // 重置月份范围 |
| | | const resetDateRange = () => { |
| | | dateRange.value = null; |
| | | // 重置为最近六个月 |
| | | dateRange.value = getLastSixMonths(); |
| | | getData(); |
| | | }; |
| | | |
| | | </script> |