<template>
|
<div style="padding: 20px;">
|
<!-- 页面标题和筛选条件 -->
|
<div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
|
<el-button
|
type="primary"
|
icon="Refresh"
|
@click="resetFilters"
|
size="default"
|
>
|
查询
|
</el-button>
|
</div>
|
|
<main class="container mx-auto px-4 pb-10">
|
<!-- 固定资产指标卡片 -->
|
<div class="grid-container">
|
<!-- 设备总数 -->
|
<el-card class="bg2">
|
<p>设备总数</p>
|
<h3>
|
{{ assetInfo.totalEquipment }}
|
</h3>
|
</el-card>
|
|
<!-- 资产原值 -->
|
<el-card class="bg3">
|
<p>资产原值</p>
|
<h3>
|
¥{{ assetInfo.totalOriginalValue }}
|
</h3>
|
</el-card>
|
|
<!-- 累计折旧 -->
|
<el-card class="bg4">
|
<p>累计折旧</p>
|
<h3>
|
¥{{ assetInfo.totalDepreciation }}
|
</h3>
|
</el-card>
|
|
<!-- 净值 -->
|
<el-card class="bg5">
|
<p>净值</p>
|
<h3>
|
¥{{ assetInfo.totalNetValue }}
|
</h3>
|
</el-card>
|
</div>
|
|
<!-- 固定资产统计图表 -->
|
<div class="grid-layout">
|
<!-- 按设备类型统计 -->
|
<el-card style="margin-bottom: 20px;">
|
<h2 class="section-title">设备类型分布</h2>
|
<div class="echarts">
|
<Echarts
|
:legend="typeDistributionLegend"
|
:chartStyle="chartStylePie"
|
:series="typeDistributionSeries"
|
:tooltip="pieTooltip"
|
style="height: 260px; width: 35%;">
|
<div class="chart-num">
|
<span style="font-size: 22px;">设备类型</span>
|
<span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
|
</div>
|
</Echarts>
|
<Echarts
|
ref="chart"
|
:chartStyle="chartStyle"
|
:grid="grid"
|
:legend="lineLegend"
|
:series="typeDistributionLineSeries"
|
:tooltip="tooltip"
|
:xAxis="xAxis"
|
:yAxis="yAxis"
|
style="height: 260px; width: 64%;"></Echarts>
|
</div>
|
</el-card>
|
</div>
|
<!-- 设备台账表格 -->
|
<el-card style="margin-bottom: 20px;">
|
<el-table
|
:data="equipmentList"
|
stripe
|
style="width: 100%"
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
>
|
<el-table-column prop="id" label="资产编号" width="120" />
|
<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.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="netValue" label="净值(元)" width="120">
|
<template #default="{ row }">
|
¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="status" label="状态" width="100">
|
<template #default="{ row }">
|
<el-tag
|
:type="getStatusTagType(row.status)"
|
size="small"
|
>
|
{{ row.status }}
|
</el-tag>
|
</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 { getLedgerPage, getAssetInfo } from "@/api/equipmentManagement/ledger";
|
import dayjs from "dayjs";
|
|
// 筛选条件
|
const dateRange = ref(null);
|
const equipmentType = ref('');
|
|
|
// 固定资产信息
|
const assetInfo = ref({
|
totalEquipment: 0,
|
totalOriginalValue: 0,
|
totalDepreciation: 0,
|
totalNetValue: 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 = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 可根据实际调整
|
|
// 饼图数据
|
const typeDistributionData = ref([]);
|
const departmentDistributionData = ref([]);
|
|
// 饼图图例
|
const typeDistributionLegend = computed(() => ({
|
show: true,
|
top: 'center',
|
left: '60%',
|
orient: 'vertical',
|
icon: 'circle',
|
data: typeDistributionData.value.map(item => item.name),
|
formatter: function(name) {
|
const item = typeDistributionData.value.find(i => i.name === name);
|
if (!item) return name;
|
return `${name} | ${item.count} 台 | ${item.amount}`;
|
},
|
textStyle: {
|
color: '#333',
|
fontSize: 14,
|
lineHeight: 26,
|
}
|
}));
|
|
|
// 饼图系列
|
const typeDistributionSeries = computed(() => [
|
{
|
type: 'pie',
|
radius: ['50%', '65%'],
|
center: ['25%', '50%'],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderColor: '#fff',
|
borderWidth: 2
|
},
|
label: {
|
show: false
|
},
|
data: typeDistributionData.value,
|
color: pieColors
|
}
|
]);
|
|
// 折线图数据
|
const typeDistributionLineSeries = ref([]);
|
|
|
// 饼图提示框
|
const pieTooltip = reactive({
|
trigger: 'item',
|
formatter: function(params) {
|
// 检查数据是否存在
|
if (!params.data) return params.name;
|
// 拼接完整内容
|
return `
|
<div>
|
<div style="color:${params.color};font-size:16px;">●</div>
|
<div>${params.name}</div>
|
<div>数量:${params.data.count} 台</div>
|
<div>金额:${params.data.amount}</div>
|
</div>
|
`;
|
}
|
});
|
|
// 选项数据
|
const equipmentTypeOptions = ref([]);
|
|
// 获取数据
|
const fetchData = async () => {
|
try {
|
// 获取固定资产汇总信息
|
const assetInfoRes = await getAssetInfo({
|
startDate: dateRange.value ? dateRange.value[0] : null,
|
endDate: dateRange.value ? dateRange.value[1] : null,
|
equipmentType: equipmentType.value
|
});
|
|
if (assetInfoRes.code === 200) {
|
assetInfo.value = assetInfoRes.data;
|
}
|
|
// 获取设备列表
|
const equipmentListRes = await getLedgerPage({
|
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
|
});
|
|
if (equipmentListRes.code === 200) {
|
equipmentList.value = equipmentListRes.data.records;
|
pagination.value.total = equipmentListRes.data.total;
|
|
// 根据 equipmentList 按 deviceName 进行分类统计
|
const deviceNameMap = {};
|
equipmentList.value.forEach(item => {
|
const deviceName = item.deviceName;
|
if (!deviceNameMap[deviceName]) {
|
deviceNameMap[deviceName] = {
|
name: deviceName,
|
count: 0,
|
totalValue: 0
|
};
|
}
|
deviceNameMap[deviceName].count += item.number || 1; // 假设 number 为设备数量
|
deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // 累加含税总价
|
});
|
|
// 转换为 typeDistributionData 格式
|
typeDistributionData.value = Object.values(deviceNameMap).map(item => ({
|
name: item.name,
|
value: item.count,
|
count: item.count,
|
amount: `¥${formatCurrency(item.totalValue)}`
|
}));
|
|
// 更新x轴数据
|
xAxis.value[0].data = typeDistributionData.value.map(item => item.name);
|
|
// 构建折线图数据
|
typeDistributionLineSeries.value = [
|
{
|
name: '设备数量',
|
type: 'line',
|
data: typeDistributionData.value.map(item => item.count)
|
}
|
];
|
}
|
} 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 = '';
|
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: #4f46e5;
|
}
|
|
.el-card {
|
position: relative;
|
border-radius: 12px;
|
padding: 14px 10px 10px 10px;
|
box-shadow: 0 2px 8px #eee;
|
|
:deep(.el-card__body) {
|
padding: 10px 20px !important;
|
}
|
|
&.bg1 {
|
background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
|
}
|
|
&.bg2 {
|
background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
|
}
|
|
&.bg3 {
|
background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
|
}
|
|
&.bg4 {
|
background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
|
}
|
|
&.bg5 {
|
background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
|
}
|
}
|
|
.grid-container {
|
/* grid 容器基础样式 */
|
display: grid;
|
gap: 1rem; /* gap-4 对应 1rem (16px) */
|
margin-bottom: 2rem; /* mb-8 对应 2rem (32px) */
|
|
p {
|
font-size: 22px;
|
margin-top: 0px;
|
color: #fff;
|
}
|
|
h3 {
|
font-size: 36px;
|
font-weight: 500;
|
font-family: 'MyCustomFont', sans-serif;
|
margin: 10px 0;
|
color: #fff;
|
}
|
}
|
|
/* 移动端默认样式 (grid-cols-1) */
|
.grid-container {
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
}
|
|
/* 小屏幕及以上 (sm:grid-cols-2) */
|
@media (min-width: 640px) {
|
.grid-container {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
}
|
|
/* 大屏幕及以上 (lg:grid-cols-5) */
|
@media (min-width: 1024px) {
|
.grid-container {
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
}
|
}
|
|
/* 卡片悬停效果增强 */
|
.el-card:hover {
|
transform: translateY(-2px);
|
}
|
|
.echarts {
|
display: flex;
|
justify-content: space-between;
|
}
|
|
/* 图表容器样式 */
|
.el-chart {
|
width: 100%;
|
height: 100%;
|
}
|
|
.section-title {
|
position: relative;
|
font-size: 18px;
|
color: #333;
|
padding-left: 10px;
|
margin-bottom: 10px;
|
font-weight: 700;
|
}
|
|
.section-title::before {
|
position: absolute;
|
left: 0;
|
top: 0px;
|
content: '';
|
width: 4px;
|
height: 18px;
|
background-color: #002FA7;
|
border-radius: 2px;
|
}
|
|
.chart-num {
|
position: absolute;
|
z-index: 3;
|
top: 92px;
|
left: 92px;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: center;
|
}
|
</style>
|