Files
im-uniapp/pages/workbench/cost/cost.vue
2025-10-14 13:58:33 +08:00

422 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<!-- 加载状态 -->
<view v-if="loading" class="loading-view">
<uni-loading-icon size="24" color="#007aff"></uni-loading-icon>
<text class="loading-text">加载中...</text>
</view>
<!-- 内容区域加载完成后显示 -->
<view v-else>
<!-- 月份选择 -->
<view class="date-picker-container">
<picker mode="date" @change="onDateChange" fields="month" :value="currentDate" class="date-picker">
<view class="picker-view">
<text>{{ formattedDate }}</text>
</view>
</picker>
</view>
<fad-collapse title="指标汇总">
<SummaryCardsVue :cardsData="cardData" />
</fad-collapse>
<fad-collapse title="月度收支">
<template #extra>
<view @click.stop>
<uni-data-checkbox @click.stop mode="tag" v-model="chartType"
:localdata="[{ value: 'line', text: '折线图' }, { value: 'column', text: '柱状图' }]" />
</view>
</template>
<BarVue :chart-data="barChartData" v-if="chartType == 'column'"></BarVue>
<LineVue :chart-data="barChartData" v-else></LineVue>
</fad-collapse>
<!-- <fad-collapse title="近六月趋势">
</fad-collapse> -->
<fad-collapse title="项目支出占比">
<PieVue :project-list="projectList"></PieVue>
</fad-collapse>
<fad-collapse title="明细表格">
<TableVue :table-data="costList" @row="showCostDetail"></TableVue>
<view v-if="tableTotal > 0" class="simple-pagination">
<!-- 上一页按钮当前页=1时禁用 -->
<button class="page-btn" :disabled="tableQueryParams.pageNum === 1" @click="handlePrevPage">
上一页
</button>
<!-- 页数/总数显示 -->
<view class="page-info">
{{ tableQueryParams.pageNum }} / {{ totalPages }}
总计{{ tableTotal }}
</view>
<!-- 下一页按钮当前页=总页数时禁用 -->
<button class="page-btn" :disabled="tableQueryParams.pageNum === totalPages" @click="handleNextPage">
下一页
</button>
</view>
</fad-collapse>
</view>
<uni-popup ref="popup" type="top">
<DetailVue :detailData='costDetail'></DetailVue>
</uni-popup>
</view>
</template>
<script>
import {
barData,
findFinanceList2,
getCostDetailList,
getCostDetailById
} from '@/api/oa/finance/finance';
import {
getExchangeRate
} from '@/api/oa/finance/exchangeRate';
import {
projectData2
} from '@/api/oa/project';
import SummaryCardsVue from './components/SummaryCards.vue';
import PieVue from './components/Pie.vue'
import BarVue from './components/Bar.vue';
import LineVue from './components/Line.vue';
import TableVue from './components/Table.vue';
import DetailVue from './components/Detail.vue';
export default {
data() {
return {
loading: true,
currentDate: '',
cardData: [], // 改为数组,存储汇总卡片数据(核心)
monthlyDataMap: {}, // 初始化月度数据映射(用于图表/卡片)
currentList: [], // barData接口返回的列表
projectList: [], // projectData2接口返回的列表
financeList: [], // findFinanceList2接口返回的列表
queryFinanceParams: {}, // 接口请求参数(需确保已定义)
dateForProject: '', // 项目接口日期参数(需确保已定义)
barChartData: {},
exchangeRate: 7,
chartType: 'column',
costList: [],
tableQueryParams: {
pageSize: 10,
pageNum: 1,
},
tableTotal: 0,
costDetail: {}
};
},
computed: {
formattedDate() {
const [year, month] = this.currentDate.split('-');
return `${year}${month}`;
},
totalPages() {
if (this.tableTotal === 0) return 0; // 无数据时总页数为0
// 总页数 = 总条数 ÷ 每页条数向上取整如15条/10条=2页
return Math.ceil(this.tableTotal / this.tableQueryParams.pageSize);
}
},
components: {
SummaryCardsVue,
PieVue,
BarVue,
LineVue,
TableVue,
DetailVue
},
onLoad() {
// 初始化日期为当前月份
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
this.currentDate = `${year}-${month.toString().padStart(2, '0')}-01`;
this.loadAllData()
},
methods: {
async loadAllExchangeRates(months) {
for (const m of months) {
if (!this.monthlyDataMap[m]) this.monthlyDataMap[m] = {};
if (this.monthlyDataMap[m].exchangeRate) continue;
this.monthlyDataMap[m].exchangeRate = this.exchangeRate;
}
},
showCostDetail(row) {
getCostDetailById(row.projectId).then(response => {
this.costDetail = response.data;
this.$refs.popup.open()
})
},
/**
* 上一页按钮事件
*/
handlePrevPage() {
if (this.tableQueryParams.pageNum > 1) {
this.tableQueryParams.pageNum--; // 页码减1
this.loadTableData(); // 重新加载当前页数据
}
},
/**
* 下一页按钮事件
*/
handleNextPage() {
// 只有当前页 < 总页数时,才能切换下一页
if (this.tableQueryParams.pageNum < this.totalPages) {
this.tableQueryParams.pageNum++; // 页码加1
this.loadTableData(); // 重新加载当前页数据
}
},
onDateChange(e) {
const value = e.detail.value;
this.currentDate = value + '-01';
this.tableQueryParams.pageNum = 1;
this.loadAllData()
},
loadTableData() {
getCostDetailList(this.tableQueryParams).then(res => {
this.costList = res.rows;
this.tableTotal = res.total;
})
},
async loadAllData() {
this.loading = true;
this.exchangeRate = await getExchangeRate()
const res = await barData(this.queryFinanceParams)
this.currentList = res.data;
const months = this.currentList.map(i => i.month);
await this.loadAllExchangeRates(months);
this.loadTableData()
Promise.all([
projectData2(this.currentDate),
findFinanceList2({
date: this.currentDate,
pageSize: 9999,
financeType: '',
pageNum: 1
})
])
.then(([projRes, finRes]) => {
this.projectList = projRes.data;
this.financeList = finRes.data;
this.computeSummaries();
})
.finally(() => {
this.loading = false;
});
},
computeSummaries() {
// 1. 获取当前选中月份格式YYYY-MM从currentDate提取
const currentMonth = this.currentDate.slice(0, 7);
// 2. 初始化月度数据映射(确保每个月份的基础结构存在)
this.currentList.forEach(item => {
const month = item.month;
this.monthlyDataMap[month] = {
outTotal: 0,
inCNY: 0,
inUSD: 0,
exchangeRate: this.monthlyDataMap[month]?.exchangeRate || 0 // 保留已有汇率
};
// 赋值支出总额从barData接口获取
this.monthlyDataMap[month].outTotal = item.totalOut;
});
// 3. 初始化统计变量(月度/项目收支)
const initStats = {
monthlyOutCNY: 0, // 月度总支出(人民币)
monthlyOutUSD: 0, // 月度总支出(美元)
monthlyInCNY: 0, // 月度总收入(人民币)
monthlyInUSD: 0, // 月度总收入(美元)
projectOutCNY: 0, // 项目支出(人民币)
projectOutUSD: 0, // 项目支出(美元)
projectInCNY: 0, // 项目收入(人民币)
projectInUSD: 0 // 项目收入(美元)
};
Object.assign(this, initStats);
// 4. 遍历财务明细,计算收支数据
this.financeList.forEach(finItem => {
const itemMonth = finItem.date.slice(0, 7); // 明细所属月份
const amount = Number(finItem.amount) || 0; // 金额转数字避免NaN
const isOut = finItem.type === '0'; // 0=支出1=收入(按业务定义)
const currency = finItem.currency === 'USD' ? 'USD' : 'CNY'; // 货币类型
// 4.1 更新月度收入数据存入monthlyDataMap
const monthData = this.monthlyDataMap[itemMonth];
if (monthData && !isOut) {
monthData[`in${currency}`] += amount;
}
// 4.2 计算「当前选中月份」的统计数据
if (itemMonth === currentMonth) {
if (isOut) {
// 支出:更新月度总支出 + 项目支出有projectId才统计项目
this[`monthlyOut${currency}`] += amount;
if (finItem.projectId) {
this[`projectOut${currency}`] += amount;
}
} else {
// 收入:更新月度总收入 + 项目收入有projectId才统计项目
this[`monthlyIn${currency}`] += amount;
if (finItem.projectId) {
this[`projectIn${currency}`] += amount;
}
}
}
});
// 5. 构建汇总卡片数据核心适配SummaryCards组件
console.log(this.monthlyDataMap, '月度数据')
const categories = Object.keys(this.monthlyDataMap);
console.log('分类', categories)
const dataList =
this.barChartData = {
categories,
series: [{
name: '支出',
data: Object.values(this.monthlyDataMap).map(item => item.outTotal)
},
{
name: '收入(USD)',
data: Object.values(this.monthlyDataMap).map(item => item.inUSD)
},
{
name: '支出(CNY)',
data: Object.values(this.monthlyDataMap).map(item => item.inCNY)
}
]
}
console.log(this.barChartData)
this.cardData = [{
title: '月度总支出',
valueCNY: Number(this.monthlyOutCNY.toFixed(2)), // 保留2位小数转数字
valueUSD: Number(this.monthlyOutUSD.toFixed(2))
},
{
title: '月度总收入',
valueCNY: Number(this.monthlyInCNY.toFixed(2)),
valueUSD: Number(this.monthlyInUSD.toFixed(2))
},
{
title: '项目支出',
valueCNY: Number(this.projectOutCNY.toFixed(2)),
valueUSD: Number(this.projectOutUSD.toFixed(2))
},
{
title: '项目收入',
valueCNY: Number(this.projectInCNY.toFixed(2)),
valueUSD: Number(this.projectInUSD.toFixed(2))
}
];
},
}
};
</script>
<style scoped>
/* 简化版分页容器:居中显示,上下留间距 */
.simple-pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx; /* 按钮与文字之间的间距 */
margin-top: 24rpx;
margin-bottom: 16rpx;
padding: 8rpx 0;
}
/* 分页按钮:统一样式,禁用时灰色 */
.page-btn {
padding: 8rpx 24rpx;
background-color: #007aff;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 24rpx;
}
/* 按钮禁用状态:灰色背景,无法点击 */
.page-btn:disabled {
background-color: #e5e5e5;
color: #999;
cursor: not-allowed;
}
/* 页数信息:灰色文字,适中大小 */
.page-info {
font-size: 24rpx;
color: #666;
}
.container {
padding: 16rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 日期选择器在折叠卡片额外内容中的样式 */
.picker-view {
display: flex;
align-items: center;
color: #666;
font-size: 24rpx;
padding: 4rpx 8rpx;
}
.icon-margin {
margin-right: 8rpx;
}
/* 图表样式调整 */
.chart-item {
width: 100%;
}
/* 加载状态样式 */
.loading-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
/* 折叠卡片间距 */
.fad-collapse {
margin-bottom: 16rpx;
}
.date-picker-container {
background-color: #fff;
border-radius: 8rpx;
padding: 16rpx;
margin-bottom: 10rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.date-picker {
width: 100%;
}
.picker-view {
text-align: center;
font-size: 28rpx;
color: #007aff;
padding: 8rpx 0;
}
</style>