| | |
| | | <template> |
| | | <div style="padding: 20px;"> |
| | | <!-- 页面标题和筛选条件 --> |
| | | <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;"> |
| | | <div class="w-full md:w-auto flex items-center gap-3"> |
| | | <el-form :inline="true"> |
| | | <el-form-item label="年份"> |
| | | <el-date-picker |
| | |
| | | |
| | | <main class="container mx-auto px-4 pb-10"> |
| | | <!-- 固定资产指标卡片 --> |
| | | <div class="grid-container"> |
| | | <div class="kpi-grid"> |
| | | <!-- 设备总数 --> |
| | | <el-card class="bg2"> |
| | | <p>设备总数</p> |
| | | <h3> |
| | | {{ assetInfo.totalEquipment }} |
| | | </h3> |
| | | </el-card> |
| | | <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> |
| | | |
| | | <!-- 资产原值 --> |
| | | <el-card class="bg3"> |
| | | <p>资产原值</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalOriginalValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <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> |
| | | |
| | | <!-- 累计折旧 --> |
| | | <el-card class="bg4"> |
| | | <p>累计折旧</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalDepreciation) }} |
| | | </h3> |
| | | </el-card> |
| | | <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> |
| | | |
| | | <!-- 净值 --> |
| | | <el-card class="bg5"> |
| | | <p>净值</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.totalNetValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <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> |
| | | |
| | | <!-- 负债 --> |
| | | <el-card class="bg2"> |
| | | <p>负债</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.debt) }} |
| | | </h3> |
| | | </el-card> |
| | | <!-- 库存资产 --> |
| | | <el-card class="bg3"> |
| | | <p>库存资产</p> |
| | | <h3> |
| | | ¥{{ formatCurrency(assetInfo.inventoryValue) }} |
| | | </h3> |
| | | </el-card> |
| | | <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="grid-layout"> |
| | | <!-- 按设备类型统计 --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <div class="chart-row"> |
| | | <!-- 设备类型分布 --> |
| | | <el-card class="chart-card"> |
| | | <h2 class="section-title">设备类型分布</h2> |
| | | <div class="echarts"> |
| | | <div class="chart-content"> |
| | | <div class="pie-wrap"> |
| | | <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;">{{ deviceTypeTotalCount }}</span> |
| | | style="height: 260px; width: 100%;" |
| | | /> |
| | | </div> |
| | | </Echarts> |
| | | <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="chart" |
| | | ref="barChart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="typeDistributionLineSeries" |
| | | :series="typeDistributionBarSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | style="height: 260px; width: 64%;"></Echarts> |
| | | :yAxis="yAxisBar" |
| | | style="height: 260px; width: 100%;" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | <!-- 设备台账表格 --> |
| | | <el-card style="margin-bottom: 20px;"> |
| | | <!-- 设备数据表 --> |
| | | <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="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" /> |
| | |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { getLedgerPage } from "@/api/equipmentManagement/ledger"; |
| | | 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); |
| | |
| | | height: '100%' // 设置图表容器的高度 |
| | | }; |
| | | |
| | | const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 可根据实际调整 |
| | | const pieColors = ['#165DFF', '#14C9C9', '#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, |
| | | } |
| | | show: false, |
| | | data: typeDistributionData.value.map(item => item.name) |
| | | })); |
| | | |
| | | |
| | |
| | | const typeDistributionSeries = computed(() => [ |
| | | { |
| | | type: 'pie', |
| | | radius: ['50%', '65%'], |
| | | center: ['25%', '50%'], |
| | | radius: ['0%', '65%'], |
| | | center: ['50%', '45%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | 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; |
| | | // 拼接完整内容 |
| | | 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 pct = params.percent != null ? params.percent.toFixed(0) : 0; |
| | | return `${params.name} ${pct}%`; |
| | | } |
| | | }); |
| | | |
| | |
| | | name: item.type || '', |
| | | value: Number(item.count || 0), |
| | | count: Number(item.count || 0), |
| | | amount: `¥${formatCurrency(item.amount || 0)}` |
| | | amount: `¥${formatCurrency(item.amount || 0)}`, |
| | | amountNum: Number(item.amount || 0) |
| | | })); |
| | | } else if (data.categories && data.categories.length > 0) { |
| | | // 如果没有 details,使用 categories、countData 和 amountData 构建 |
| | |
| | | name: category, |
| | | value: Number(data.countData[index] || 0), |
| | | count: Number(data.countData[index] || 0), |
| | | amount: `¥${formatCurrency(data.amountData[index] || 0)}` |
| | | amount: `¥${formatCurrency(data.amountData[index] || 0)}`, |
| | | amountNum: Number(data.amountData[index] || 0) |
| | | })); |
| | | } else { |
| | | typeDistributionData.value = []; |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | /* 基础样式补充 */ |
| | | :root { |
| | | --el-color-primary: #4f46e5; |
| | | --el-color-primary: #1890ff; |
| | | } |
| | | |
| | | .el-card { |
| | | position: relative; |
| | | border-radius: 12px; |
| | | padding: 14px 10px 10px 10px; |
| | | box-shadow: 0 2px 8px #eee; |
| | | /* 页面背景 */ |
| | | 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: 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; |
| | | padding: 16px 20px; |
| | | } |
| | | } |
| | | |
| | | .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-6) */ |
| | | @media (min-width: 1024px) { |
| | | .grid-container { |
| | | grid-template-columns: repeat(6, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | /* 卡片悬停效果增强 */ |
| | | .el-card:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .echarts { |
| | | .chart-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | /* 图表容器样式 */ |
| | | .el-chart { |
| | | .pie-wrap { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 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: 10px; |
| | | margin-bottom: 10px; |
| | | padding-left: 12px; |
| | | margin-bottom: 16px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .section-title::before { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0px; |
| | | top: 2px; |
| | | content: ''; |
| | | width: 4px; |
| | | height: 18px; |
| | | background-color: #002FA7; |
| | | background: #1890ff; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .chart-num { |
| | | position: absolute; |
| | | z-index: 3; |
| | | top: 92px; |
| | | left: 92px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | /* 表格卡片 */ |
| | | .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: center; |
| | | 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> |