| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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 } 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> |