323 lines
10 KiB
Vue
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>
|