Files
fad_oa/ruoyi-ui/src/views/oa/finance/salary/dashboard/index.vue
2026-04-13 17:04:38 +08:00

323 lines
10 KiB
Vue

<template>
<div style="padding:20px">
<!-- 时间范围选择 -->
<el-row class="mb8">
<el-col :span="4">
<el-date-picker
v-model="selectedTime"
type="month"
placeholder="选择年月"
value-format="yyyy-MM-01"
>
</el-date-picker>
</el-col>
</el-row>
<!-- 卡片数据展示 -->
<el-row :gutter="20" v-loading="loading" style="margin-bottom: 20px">
<el-col :span="24">
<el-row :gutter="20">
<el-col :span="4" v-for="card in cards" :key="card.title">
<statistic-card
:title="card.title"
:value="card.value"
:sub-text="card.subText"
:growth="card.growth"
/>
</el-col>
</el-row>
</el-col>
</el-row>
<!-- 图表区域第一行 -->
<el-row :gutter="20" style="margin-bottom: 20px">
<el-col :span="12">
<el-card v-loading="monthlyExpenseLoading">
<div slot="header" class="clearfix">
<span>月度支出汇总</span>
</div>
<bar-chart :data="monthlyExpenseData" />
</el-card>
</el-col>
<el-col :span="12">
<el-card v-loading="salaryStructureLoading">
<div slot="header" class="clearfix">
<span>工资构成分析</span>
</div>
<pie-chart :data="salaryStructureData" :categories="salaryStructureCategories" />
</el-card>
</el-col>
</el-row>
<!-- 图表区域第二行 -->
<el-row :gutter="20" style="margin-bottom: 20px">
<el-col :span="12">
<el-card v-loading="trendLoading">
<div slot="header" class="clearfix">
<span>工资趋势分析</span>
</div>
<line-chart :data="trendData" />
</el-card>
</el-col>
<el-col :span="12">
<el-card v-loading="insuranceLoading">
<div slot="header" class="clearfix">
<span>社保公积金统计</span>
</div>
<bar-chart :data="insuranceData" :type="'insurance'" />
</el-card>
</el-col>
</el-row>
<!-- 统计表格 -->
<el-row :gutter="20">
<el-col :span="12">
<el-card v-loading="unitTableLoading">
<div slot="header" class="clearfix">
<span>单位统计</span>
</div>
<unit-table :data="unitTableData" :total="unitTableTotal" />
</el-card>
</el-col>
<el-col :span="12">
<el-card v-loading="deptTableLoading">
<div slot="header" class="clearfix">
<span>部门统计</span>
</div>
<department-table :data="deptTableData" :total="deptTableTotal" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getAllData } from '@/api/oa/finance/dashboard';
import BarChart from './components/BarChart.vue';
import DepartmentTable from './components/DepartmentTable.vue';
import UnitTable from './components/UnitTable.vue';
import LineChart from './components/LineChart.vue';
import PieChart from './components/PieChart.vue';
import StatisticCard from './components/StatisticCard.vue';
export default {
name: 'SalaryDashboard',
components: {
StatisticCard,
BarChart,
PieChart,
LineChart,
DepartmentTable,
UnitTable
},
created() {
const now = new Date();
this.selectedTime = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-01`;
},
watch: {
selectedTime: {
handler(val) {
if (val) {
const date = new Date(val);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const salaryPeriod = `${year}-${month}`;
this.fetchData(salaryPeriod);
}
},
immediate: true
}
},
data() {
return {
selectedTime: null,
loading: false,
monthlyExpenseLoading: false,
salaryStructureLoading: false,
trendLoading: false,
insuranceLoading: false,
unitTableLoading: false,
deptTableLoading: false,
cards: [],
monthlyExpenseData: [],
salaryStructureCategories: [],
salaryStructureData: [],
trendData: [],
insuranceData: [],
unitTableData: [],
unitTableTotal: 0,
deptTableData: [],
deptTableTotal: 0,
};
},
methods: {
async fetchData(salaryPeriod) {
this.loading = true;
this.monthlyExpenseLoading = true;
this.salaryStructureLoading = true;
this.trendLoading = true;
this.insuranceLoading = true;
this.unitTableLoading = true;
this.deptTableLoading = true;
try {
const response = await getAllData({
salaryPeriod: salaryPeriod
});
if(response.code === 200) {
const { data } = response;
const { cardData, chartData, unitStats, deptStats } = data;
// 处理卡片数据
this.processCardData(cardData);
// 处理图表数据
this.processChartData(chartData);
// 处理表格数据
this.processTableData(unitStats, deptStats);
}
} catch (error) {
console.error('获取数据失败:', error);
this.$message.error('获取数据失败,请稍后重试');
} finally {
this.loading = false;
this.monthlyExpenseLoading = false;
this.salaryStructureLoading = false;
this.trendLoading = false;
this.insuranceLoading = false;
this.unitTableLoading = false;
this.deptTableLoading = false;
}
},
// 处理卡片数据
processCardData(cardData) {
this.cards = [
{
title: '总实发工资',
value: this.formatCurrency(cardData.totalNetSalary),
subText: `较上月${this.getGrowthText(cardData.lastMonthNetSalaryRate)}`,
growth: this.getGrowthDirection(cardData.lastMonthNetSalaryRate)
},
{
title: '总应发工资',
value: this.formatCurrency(cardData.totalGrossSalary),
subText: `较上月${this.getGrowthText(cardData.lastMonthGrossSalaryRate)}`,
growth: this.getGrowthDirection(cardData.lastMonthGrossSalaryRate)
},
{
title: '单位总支出',
value: this.formatCurrency(cardData.totalUnitExpense),
subText: `较上月${this.getGrowthText(cardData.lastMonthUnitExpenseRate)}`,
growth: this.getGrowthDirection(cardData.lastMonthUnitExpenseRate)
},
{
title: '人均实发工资',
value: this.formatCurrency(cardData.avgNetSalary),
subText: `较上月${this.getGrowthText(cardData.lastMonthAvgNetSalaryRate)}`,
growth: this.getGrowthDirection(cardData.lastMonthAvgNetSalaryRate)
},
{
title: '同比增长率',
value: `${cardData.yearOnYearGrowthRate || 0}%`,
subText: '较去年同期',
growth: this.getGrowthDirection(cardData.yearOnYearGrowthRate)
},
{
title: '员工总数',
value: `${cardData.totalEmployeeCount || 0}`,
subText: `${cardData.unitCount || 0}个单位`,
growth: 'neutral'
}
];
},
// 处理图表数据
processChartData(chartData) {
// 月度支出数据
this.monthlyExpenseData = chartData.monthlyExpenses?.map(item => ({
month: `${item.month}`,
netSalary: item.totalNetSalary,
grossSalary: item.totalGrossSalary,
unitExpense: item.totalUnitExpense
})) || [];
// 工资构成分析数据
this.salaryStructureCategories = chartData.salaryStructures?.map(item => item.itemName) || [];
this.salaryStructureData = chartData.salaryStructures?.map(item => ({
name: item.itemName,
value: item.totalAmount,
percentage: item.percentage
})) || [];
// 趋势分析数据
if (chartData.trendData) {
this.trendData = chartData.trendData.months?.map((month, index) => ({
month: `${month}`,
netSalary: chartData.trendData.netSalaryTrend?.[index] || 0,
grossSalary: chartData.trendData.grossSalaryTrend?.[index] || 0,
avgNetSalary: chartData.trendData.avgNetSalaryTrend?.[index] || 0
})) || [];
}
// 社保公积金统计数据
this.insuranceData = chartData.insuranceStats?.map(item => ({
itemName: item.itemName,
personal: item.personalTotal,
enterprise: item.enterpriseTotal,
total: item.total
})) || [];
},
// 处理表格数据
processTableData(unitStats, deptStats) {
// 单位统计数据
this.unitTableData = unitStats?.rows?.map(item => ({
unitName: item.unitName,
employeeCount: item.employeeCount,
totalNetSalary: this.formatCurrency(item.totalNetSalary),
totalGrossSalary: this.formatCurrency(item.totalGrossSalary),
totalUnitExpense: this.formatCurrency(item.totalUnitExpense),
avgNetSalary: this.formatCurrency(item.avgNetSalary),
yearOnYearGrowthRate: `${item.yearOnYearGrowthRate >= 0 ? '+' : ''}${item.yearOnYearGrowthRate}%`,
salaryPeriod: item.salaryPeriod
})) || [];
this.unitTableTotal = unitStats?.total || 0;
// 部门统计数据
this.deptTableData = deptStats?.rows?.map(item => ({
deptName: item.deptName,
employeeCount: item.employeeCount,
totalNetSalary: this.formatCurrency(item.totalNetSalary),
totalGrossSalary: this.formatCurrency(item.totalGrossSalary),
avgNetSalary: this.formatCurrency(item.avgNetSalary),
avgGrossSalary: this.formatCurrency(item.avgGrossSalary),
yearOnYearGrowthRate: `${item.yearOnYearGrowthRate >= 0 ? '+' : ''}${item.yearOnYearGrowthRate}%`
})) || [];
this.deptTableTotal = deptStats?.total || 0;
},
// 格式化货币
formatCurrency(amount) {
if (!amount) return '¥ 0';
return `¥ ${Number(amount).toLocaleString()}`;
},
// 获取增长文本
getGrowthText(rate) {
if (!rate) return '无变化';
const direction = rate >= 0 ? '增长' : '下降';
return `${direction} ${Math.abs(rate)}%`;
},
// 获取增长方向
getGrowthDirection(rate) {
if (!rate) return 'neutral';
return rate >= 0 ? 'up' : 'down';
}
}
};
</script>