<template>
|
<div style="padding: 20px;">
|
<!-- 页面标题和筛选条件 -->
|
<div class="w-full md:w-auto flex items-center gap-3">
|
<el-form :inline="true">
|
<el-form-item label="年份">
|
<el-date-picker
|
v-model="selectedYear"
|
type="year"
|
placeholder="请选择年份"
|
format="YYYY"
|
value-format="YYYY"
|
clearable
|
@change="fetchData()"
|
style="width: 200px"
|
:disabled-date="(date) => date.getFullYear() > new Date().getFullYear()"
|
/>
|
</el-form-item>
|
<el-form-item>
|
<el-button
|
type="primary"
|
icon="Refresh"
|
@click="resetFilters"
|
size="default"
|
>
|
重置
|
</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<main class="container mx-auto px-4 pb-10">
|
<!-- 固定资产指标卡片 -->
|
<div class="kpi-grid">
|
<!-- 设备总数 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-blue"></span>
|
<span class="kpi-title">设备总数</span>
|
<div class="kpi-value">{{ assetInfo.totalEquipment }}个</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-blue">
|
<img :src="iconBlue" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
|
<!-- 资产原值 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-orange"></span>
|
<span class="kpi-title">资产原值</span>
|
<div class="kpi-value">¥{{ formatCurrency(assetInfo.totalOriginalValue) }}</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-orange">
|
<img :src="iconWalletOrange" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
|
<!-- 累计折旧 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-green"></span>
|
<span class="kpi-title">累计折旧</span>
|
<div class="kpi-value">¥{{ formatCurrency(assetInfo.totalDepreciation) }}</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-green">
|
<img :src="iconGreen" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
|
<!-- 库存资产 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-pink"></span>
|
<span class="kpi-title">库存资产</span>
|
<div class="kpi-value">¥{{ formatCurrency(assetInfo.inventoryValue) }}</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-pink">
|
<img :src="iconPink" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
|
<!-- 净值 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-yellow"></span>
|
<span class="kpi-title">净值</span>
|
<div class="kpi-value">¥{{ formatCurrency(assetInfo.totalNetValue) }}</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-yellow">
|
<img :src="iconYellow" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
|
<!-- 负债 -->
|
<div class="kpi-card">
|
<div class="kpi-left">
|
<span class="kpi-dot kpi-dot-red"></span>
|
<span class="kpi-title">负债</span>
|
<div class="kpi-value">¥{{ formatCurrency(assetInfo.debt) }}</div>
|
</div>
|
<div class="kpi-icon-wrap kpi-icon-red">
|
<img :src="iconWalletRed" alt="" class="kpi-icon" />
|
</div>
|
</div>
|
</div>
|
|
<!-- 固定资产统计图表 -->
|
<div class="chart-row">
|
<!-- 设备类型分布 -->
|
<el-card class="chart-card">
|
<h2 class="section-title">设备类型分布</h2>
|
<div class="chart-content">
|
<div class="pie-wrap">
|
<Echarts
|
:legend="typeDistributionLegend"
|
:chartStyle="chartStylePie"
|
:series="typeDistributionSeries"
|
:tooltip="pieTooltip"
|
style="height: 260px; width: 100%;"
|
/>
|
</div>
|
<div class="type-cards">
|
<div class="type-card" v-for="(item, index) in typeDistributionData" :key="index">
|
<span class="type-name">{{ item.name }}</span>
|
<span class="type-count">{{ item.count }}</span>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
<!-- 设备金额分析 -->
|
<el-card class="chart-card">
|
<h2 class="section-title">设备金额分析</h2>
|
<div class="bar-chart-wrap">
|
<Echarts
|
ref="barChart"
|
:chartStyle="chartStyle"
|
:grid="grid"
|
:legend="lineLegend"
|
:series="typeDistributionBarSeries"
|
:tooltip="tooltip"
|
:xAxis="xAxis"
|
:yAxis="yAxisBar"
|
style="height: 260px; width: 100%;"
|
/>
|
</div>
|
</el-card>
|
</div>
|
<!-- 设备数据表 -->
|
<el-card class="table-card">
|
<h2 class="section-title">设备数据表</h2>
|
<el-table
|
:data="equipmentList"
|
stripe
|
style="width: 100%"
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
>
|
<el-table-column type="index" label="序号" width="60" :index="(index) => (pagination.currentPage - 1) * pagination.pageSize + index + 1" />
|
<el-table-column prop="deviceName" label="设备名称" width="250" />
|
<el-table-column prop="deviceModel" label="规格型号" min-width="150" />
|
<el-table-column prop="supplierName" label="供应商" min-width="120" />
|
<el-table-column prop="unit" label="单位" width="120" />
|
<el-table-column prop="number" label="数量" width="120" />
|
<el-table-column prop="originalValue" label="原值(元)" width="120">
|
<template #default="{ row }">
|
{{ formatCurrency(row.taxIncludingPriceTotal) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="depreciation" label="累计折旧(元)" width="140">
|
<template #default="{ row }">
|
{{ formatCurrency(row.deprAmount) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="netValue" label="净值(元)" width="120">
|
<template #default="{ row }">
|
{{ formatCurrency(row.netValue) }}
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination
|
@size-change="handleSizeChange"
|
@current-change="handleCurrentChange"
|
:current-page="pagination.currentPage"
|
:page-sizes="[10, 20, 50, 100]"
|
:page-size="pagination.pageSize"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="pagination.total"
|
/>
|
</div>
|
</el-card>
|
</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 { getAccountingTotal, getDeviceTypeDistribution, getCalculateDepreciation } from "@/api/financialManagement/accounting";
|
import dayjs from "dayjs";
|
import iconBlue from '@/assets/icons/png/blue@2x.png';
|
import iconWalletOrange from '@/assets/icons/png/walletOrange@2x.png';
|
import iconGreen from '@/assets/icons/png/green@2x.png';
|
import iconPink from '@/assets/icons/png/pink@2x.png';
|
import iconYellow from '@/assets/icons/png/yellow@2x.png';
|
import iconWalletRed from '@/assets/icons/png/walletRed@2x.png';
|
|
// 筛选条件
|
const dateRange = ref(null);
|
const equipmentType = ref('');
|
const selectedYear = ref(dayjs().format('YYYY')); // 默认当前年份
|
|
|
// 固定资产信息
|
const assetInfo = ref({
|
totalEquipment: 0, // deviceTotal
|
totalOriginalValue: 0, // deviceAmount
|
totalDepreciation: 0, // deprAmount
|
totalNetValue: 0, // netValue
|
debt: 0, // 负债
|
inventoryValue: 0 // 库存资产
|
});
|
|
// 设备类型总数(用于图表显示)
|
const deviceTypeTotalCount = ref(0);
|
|
// 设备列表
|
const equipmentList = ref([]);
|
const pagination = ref({
|
currentPage: 1,
|
pageSize: 10,
|
total: 0
|
});
|
|
// 图表配置
|
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 xAxis = ref([
|
{
|
type: 'category',
|
axisTick: { show: true, alignWithLabel: true },
|
data: [],
|
},
|
]);
|
|
const yAxis = [
|
{
|
type: 'value',
|
name: '数量/金额', // 左侧y轴
|
position: 'left',
|
min: 0,
|
// 坐标轴名称样式
|
nameTextStyle: {
|
color: '#000',
|
fontSize: 14,
|
},
|
}
|
];
|
|
const chartStylePie = {
|
width: '100%',
|
height: '100%' // 设置图表容器的高度
|
};
|
|
const pieColors = ['#165DFF', '#14C9C9', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 可根据实际调整
|
|
// 饼图数据
|
const typeDistributionData = ref([]);
|
const departmentDistributionData = ref([]);
|
|
// 饼图图例(悬停显示名称+占比,图例放下方卡片展示)
|
const typeDistributionLegend = computed(() => ({
|
show: false,
|
data: typeDistributionData.value.map(item => item.name)
|
}));
|
|
|
// 饼图系列
|
const typeDistributionSeries = computed(() => [
|
{
|
type: 'pie',
|
radius: ['0%', '65%'],
|
center: ['50%', '45%'],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderColor: '#fff',
|
borderWidth: 2
|
},
|
label: { show: false },
|
data: typeDistributionData.value,
|
color: pieColors
|
}
|
]);
|
|
// 折线图数据
|
const typeDistributionLineSeries = ref([]);
|
// 柱状图数据(设备金额分析)
|
const typeDistributionBarSeries = computed(() => [
|
{
|
name: '销售额',
|
type: 'bar',
|
data: typeDistributionData.value.map(item => (item.amountNum != null ? item.amountNum : 0)),
|
itemStyle: { color: '#13C2C2' }
|
}
|
]);
|
// 柱状图 Y 轴
|
const yAxisBar = [
|
{
|
type: 'value',
|
name: '销售额(万元)',
|
position: 'left',
|
min: 0,
|
nameTextStyle: { color: '#000', fontSize: 14 },
|
splitLine: { lineStyle: { color: '#f0f0f0' } }
|
}
|
];
|
|
|
// 饼图提示框(图内样式:名称 + 占比)
|
const pieTooltip = reactive({
|
trigger: 'item',
|
formatter: function(params) {
|
if (!params.data) return params.name;
|
const pct = params.percent != null ? params.percent.toFixed(0) : 0;
|
return `${params.name} ${pct}%`;
|
}
|
});
|
|
// 选项数据
|
const equipmentTypeOptions = ref([]);
|
|
// 获取数据
|
const fetchData = async () => {
|
try {
|
// 获取固定资产汇总信息
|
const assetInfoRes = await getAccountingTotal({
|
startDate: dateRange.value ? dateRange.value[0] : null,
|
endDate: dateRange.value ? dateRange.value[1] : null,
|
equipmentType: equipmentType.value,
|
year: selectedYear.value
|
});
|
|
if (assetInfoRes.code === 200) {
|
// 映射后端字段到前端字段
|
const data = assetInfoRes.data;
|
assetInfo.value = {
|
totalEquipment: data.deviceTotal || 0, // 设备总数
|
totalOriginalValue: data.deviceAmount || 0, // 资产原值
|
totalDepreciation: data.deprAmount || 0, // 累计折旧
|
totalNetValue: data.netValue || 0, // 净值
|
debt: data.debt || 0, // 负债
|
inventoryValue: data.inventoryValue || 0 // 库存资产
|
};
|
}
|
|
// 获取设备类型分布数据(饼图和折线图)
|
const distributionRes = await getDeviceTypeDistribution({
|
startDate: dateRange.value ? dateRange.value[0] : null,
|
endDate: dateRange.value ? dateRange.value[1] : null,
|
equipmentType: equipmentType.value,
|
year: selectedYear.value
|
});
|
|
if (distributionRes.code === 200) {
|
const data = distributionRes.data;
|
|
// 更新设备类型总数
|
deviceTypeTotalCount.value = data.totalCount || 0;
|
|
// 转换饼图数据格式
|
if (data.details && data.details.length > 0) {
|
typeDistributionData.value = data.details.map(item => ({
|
name: item.type || '',
|
value: Number(item.count || 0),
|
count: Number(item.count || 0),
|
amount: `¥${formatCurrency(item.amount || 0)}`,
|
amountNum: Number(item.amount || 0)
|
}));
|
} else if (data.categories && data.categories.length > 0) {
|
// 如果没有 details,使用 categories、countData 和 amountData 构建
|
typeDistributionData.value = data.categories.map((category, index) => ({
|
name: category,
|
value: Number(data.countData[index] || 0),
|
count: Number(data.countData[index] || 0),
|
amount: `¥${formatCurrency(data.amountData[index] || 0)}`,
|
amountNum: Number(data.amountData[index] || 0)
|
}));
|
} else {
|
typeDistributionData.value = [];
|
}
|
|
// 更新x轴数据
|
xAxis.value[0].data = data.categories || typeDistributionData.value.map(item => item.name);
|
|
// 构建折线图数据
|
typeDistributionLineSeries.value = [
|
{
|
name: '设备数量',
|
type: 'line',
|
data: data.countData || typeDistributionData.value.map(item => item.count)
|
}
|
];
|
}
|
|
// 获取设备列表(折旧计算数据)
|
const equipmentListRes = await getCalculateDepreciation({
|
current: pagination.value.currentPage,
|
size: pagination.value.pageSize,
|
startDate: dateRange.value ? dateRange.value[0] : null,
|
endDate: dateRange.value ? dateRange.value[1] : null,
|
equipmentType: equipmentType.value,
|
year: selectedYear.value
|
});
|
|
if (equipmentListRes.code === 200) {
|
// 如果返回的是分页数据
|
if (equipmentListRes.data.records) {
|
equipmentList.value = equipmentListRes.data.records;
|
pagination.value.total = equipmentListRes.data.total;
|
} else if (Array.isArray(equipmentListRes.data)) {
|
// 如果返回的是数组
|
equipmentList.value = equipmentListRes.data;
|
pagination.value.total = equipmentListRes.data.length;
|
} else {
|
equipmentList.value = [];
|
pagination.value.total = 0;
|
}
|
}
|
} catch (error) {
|
console.error('获取固定资产数据失败:', error);
|
}
|
};
|
|
// 初始化
|
onMounted(() => {
|
// 获取列表数据
|
fetchData();
|
});
|
|
// 格式化货币
|
const formatCurrency = (value) => {
|
if (!value) return '0.00';
|
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
};
|
|
// 获取状态标签类型
|
const getStatusTagType = (status) => {
|
switch (status) {
|
case '在用':
|
return 'success';
|
case '闲置':
|
return 'info';
|
case '维修中':
|
return 'warning';
|
case '报废':
|
return 'danger';
|
default:
|
return 'info';
|
}
|
};
|
|
// 重置筛选条件
|
const resetFilters = () => {
|
dateRange.value = null;
|
equipmentType.value = '';
|
selectedYear.value = dayjs().format('YYYY'); // 重置为当前年份
|
fetchData();
|
};
|
|
// 分页处理
|
const handleSizeChange = (size) => {
|
pagination.value.pageSize = size;
|
fetchData();
|
};
|
|
const handleCurrentChange = (page) => {
|
pagination.value.currentPage = page;
|
fetchData();
|
};
|
</script>
|
|
<style scoped lang="scss">
|
:root {
|
--el-color-primary: #1890ff;
|
}
|
|
/* 页面背景 */
|
main {
|
background: #f5f5f5;
|
padding: 0;
|
margin: 0 -20px;
|
padding: 0 20px 20px;
|
}
|
|
/* KPI 卡片网格 */
|
.kpi-grid {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 16px;
|
margin-bottom: 20px;
|
}
|
|
@media (max-width: 1024px) {
|
.kpi-grid {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
}
|
|
@media (max-width: 640px) {
|
.kpi-grid {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
/* KPI 卡片 - 白底、圆角、阴影 */
|
.kpi-card {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
background: #fff;
|
border-radius: 8px;
|
padding: 16px 20px;
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
min-height: 100px;
|
}
|
|
.kpi-left {
|
flex: 1;
|
min-width: 0;
|
}
|
|
.kpi-dot {
|
display: inline-block;
|
width: 8px;
|
height: 8px;
|
border-radius: 50%;
|
margin-right: 6px;
|
vertical-align: middle;
|
}
|
|
.kpi-dot-blue { background: #1890ff; }
|
.kpi-dot-orange { background: #fa8c16; }
|
.kpi-dot-green { background: #52c41a; }
|
.kpi-dot-pink { background: #eb2f96; }
|
.kpi-dot-yellow { background: #facc14; }
|
.kpi-dot-red { background: #f5222d; }
|
|
.kpi-title {
|
font-size: 14px;
|
color: #333;
|
vertical-align: middle;
|
}
|
|
.kpi-value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #333;
|
margin-top: 8px;
|
line-height: 1.2;
|
}
|
|
/* 右侧图标方块 */
|
.kpi-icon-wrap {
|
width: 48px;
|
height: 48px;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex-shrink: 0;
|
}
|
|
.kpi-icon-blue { background: #e6f7ff; }
|
.kpi-icon-orange { background: #fff7e6; }
|
.kpi-icon-green { background: #f6ffed; }
|
.kpi-icon-pink { background: #fff0f6; }
|
.kpi-icon-yellow { background: #fffbe6; }
|
.kpi-icon-red { background: #fff1f0; }
|
|
.kpi-icon {
|
width: 28px;
|
height: 28px;
|
object-fit: contain;
|
}
|
|
/* 图表区域两列 */
|
.chart-row {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
@media (max-width: 1024px) {
|
.chart-row {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
.chart-card {
|
border-radius: 8px;
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
:deep(.el-card__body) {
|
padding: 16px 20px;
|
}
|
}
|
|
.chart-content {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
|
.pie-wrap {
|
position: relative;
|
width: 100%;
|
height: 260px;
|
}
|
|
.type-cards {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12px;
|
justify-content: center;
|
margin-top: 12px;
|
}
|
|
.type-card {
|
background: #fafafa;
|
border-radius: 6px;
|
padding: 8px 16px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
min-width: 80px;
|
}
|
|
.type-name {
|
font-size: 12px;
|
color: #666;
|
}
|
|
.type-count {
|
font-size: 16px;
|
font-weight: 600;
|
color: #333;
|
margin-top: 4px;
|
}
|
|
.bar-chart-wrap {
|
width: 100%;
|
height: 260px;
|
}
|
|
/* 区块标题 - 左侧蓝色竖线 */
|
.section-title {
|
position: relative;
|
font-size: 18px;
|
color: #333;
|
padding-left: 12px;
|
margin-bottom: 16px;
|
font-weight: 700;
|
}
|
|
.section-title::before {
|
position: absolute;
|
left: 0;
|
top: 2px;
|
content: '';
|
width: 4px;
|
height: 18px;
|
background: #1890ff;
|
border-radius: 2px;
|
}
|
|
/* 表格卡片 */
|
.table-card {
|
border-radius: 8px;
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
:deep(.el-card__body) {
|
padding: 16px 20px;
|
}
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
align-items: center;
|
}
|
|
:deep(.el-pagination) {
|
--el-pagination-button-bg-color: #fff;
|
}
|
|
:deep(.el-pager li.is-active) {
|
background: #1890ff;
|
}
|
</style>
|