| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="inventory-statistics"> |
| | | <!-- çé表å --> |
| | | <div class="filter-form"> |
| | | <el-form :model="filterForm" inline> |
| | | <!-- <el-form-item label="æ¶é´èå´">--> |
| | | <!-- <el-date-picker--> |
| | | <!-- v-model="filterForm.dateRange"--> |
| | | <!-- type="daterange"--> |
| | | <!-- range-separator="è³"--> |
| | | <!-- start-placeholder="å¼å§æ¥æ"--> |
| | | <!-- end-placeholder="ç»ææ¥æ"--> |
| | | <!-- />--> |
| | | <!-- </el-form-item>--> |
| | | <!-- <el-form-item label="ä¾åºååç§°">--> |
| | | <!-- <el-input v-model="filterForm.supplierName" style="width: 240px" placeholder="请è¾å
¥" clearable prefix-icon="Search" />--> |
| | | <!-- </el-form-item>--> |
| | | <!-- <el-form-item label="产ååç§°">--> |
| | | <!-- <el-input v-model="filterForm.productCategory" style="width: 240px" placeholder="请è¾å
¥" clearable prefix-icon="Search" />--> |
| | | <!-- </el-form-item>--> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æ¥è¯¢</el-button> |
| | | <!-- <el-button @click="handleReset">éç½®</el-button>--> |
| | | <!-- <el-button type="success" @click="handleExport">导åº</el-button>--> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡æ±æ»å¡ç --> |
| | | <div class="summary-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">æ»åºåæ°é</p> |
| | | <p class="summary-value">{{ summaryData.totalInventoryCount }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">æ»åºåéé¢</p> |
| | | <p class="summary-value">Â¥{{ summaryData.totalInventoryValue }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">åºåå卿°é</p> |
| | | <p class="summary-value">{{ summaryData.inventoryChangeCount }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="summary-card"> |
| | | <div class="summary-item"> |
| | | <p class="summary-title">åºååå¨éé¢</p> |
| | | <p class="summary-value">Â¥{{ summaryData.inventoryChangeValue }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="chart-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºååç±»å æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div id="category-pie-chart" style="height: 400px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºåéé¢è¶å¿</span> |
| | | </div> |
| | | </template> |
| | | <div id="amount-trend-chart" style="height: 400px;"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- æ°æ®è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | v-loading="loading" |
| | | border |
| | | style="width: 100%" |
| | | :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" width="240" show-overflow-tooltip /> |
| | | <el-table-column label="产å" prop="productCategory" min-width="100" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" width="70" show-overflow-tooltip /> |
| | | <el-table-column label="å
¥åºæ°é" prop="inboundNum" width="90" show-overflow-tooltip /> |
| | | <el-table-column label="åºåæ°é" prop="inboundNum0" width="90" show-overflow-tooltip /> |
| | | <el-table-column label="å«ç¨åä»·" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å«ç¨æ»ä»·" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="80" show-overflow-tooltip /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å
¥åºäºº" prop="createBy" width="100" show-overflow-tooltip /> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import {getStockInChartData, getStockInPage} from "@/api/inventoryManagement/stockIn.js"; |
| | | |
| | | // ç¶æåé |
| | | const loading = ref(false) |
| | | const total = ref(0) |
| | | const tableData = ref([]) |
| | | const summaryData = ref({}) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | const categoryPieChart = ref(null) |
| | | const amountTrendChart = ref(null) |
| | | |
| | | // çé表å |
| | | const filterForm = reactive({ |
| | | dateRange: [], |
| | | supplierName: '', |
| | | productCategory: '' |
| | | }) |
| | | |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | loadData() |
| | | } |
| | | |
| | | // åå§åæ°æ® |
| | | onMounted(() => { |
| | | loadSummaryData() |
| | | loadData() |
| | | }) |
| | | |
| | | // å è½½ç»è®¡æ±æ»æ°æ® |
| | | const loadSummaryData = () => { |
| | | getStockInChartData().then(res => { |
| | | summaryData.value = res.data |
| | | }) |
| | | } |
| | | |
| | | // å è½½åºåæ°æ® |
| | | const loadData = () => { |
| | | loading.value = true |
| | | getStockInPage({ ...filterForm, ...page }).then(res => { |
| | | loading.value = false |
| | | tableData.value = res.data.records |
| | | total.value = res.data.total |
| | | console.log('res', res.data.records) |
| | | |
| | | // æ°æ®å è½½å®æåæ¸²æå¾è¡¨ |
| | | nextTick(() => { |
| | | renderCategoryPieChart() |
| | | renderAmountTrendChart() |
| | | }) |
| | | }).catch(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| | | // 渲æåç±»å æ¯é¥¼å¾ |
| | | const renderCategoryPieChart = () => { |
| | | if (!categoryPieChart.value) { |
| | | categoryPieChart.value = echarts.init(document.getElementById('category-pie-chart')) |
| | | } |
| | | // æ ¹æ® tableData æ productCategory åç±»å¹¶è®¡ç® inboundNum0 æ°éæ»å |
| | | const categoryMap = tableData.value.reduce((acc, cur) => { |
| | | acc[cur.productCategory] = (acc[cur.productCategory] || 0) + cur.inboundNum0 |
| | | return acc |
| | | }, {}) |
| | | |
| | | // å°åç±»ç»æè½¬æ¢ä¸º ECharts 饼徿éçæ°æ®æ ¼å¼ |
| | | const categoryData = Object.entries(categoryMap).map(([name, value]) => ({ |
| | | name: name, |
| | | value: value |
| | | })) |
| | | const option = { |
| | | title: { |
| | | text: 'åºååç±»å æ¯', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åºååç±»', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | avoidLabelOverlap: false, |
| | | itemStyle: { |
| | | borderRadius: 10, |
| | | borderColor: '#fff', |
| | | borderWidth: 2 |
| | | }, |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '16', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | data: categoryData |
| | | } |
| | | ] |
| | | } |
| | | |
| | | categoryPieChart.value.setOption(option) |
| | | } |
| | | // 渲æéé¢è¶å¿æçº¿å¾ |
| | | const renderAmountTrendChart = () => { |
| | | if (!amountTrendChart.value) { |
| | | amountTrendChart.value = echarts.init(document.getElementById('amount-trend-chart')) |
| | | } |
| | | // ææä»½åç»å¹¶è®¡ç®taxInclusiveTotalPriceæ»å |
| | | const monthlyAmounts = tableData.value.reduce((acc, cur) => { |
| | | const date = new Date(cur.createTime); |
| | | const month = date.getMonth() + 1; |
| | | |
| | | // ç¡®ä¿monthå¨1-12èå´å
|
| | | if (month >= 1 && month <= 12) { |
| | | acc[month] = (acc[month] || 0) + cur.taxInclusiveTotalPrice; |
| | | } |
| | | return acc; |
| | | }, {}); |
| | | |
| | | // çæ12个æçæ°æ®ï¼ç¼ºå¤±æä»½ç¨0ä»£æ¿ |
| | | const amounts = []; |
| | | for (let i = 1; i <= 12; i++) { |
| | | amounts.push(monthlyAmounts[i] || 0); |
| | | } |
| | | const dates = ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åºåéé¢è¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: '{b}: ¥{c}' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: dates |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: 'Â¥{value}' |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'åºåéé¢', |
| | | type: 'line', |
| | | data: amounts, |
| | | smooth: true, |
| | | areaStyle: {} |
| | | } |
| | | ] |
| | | } |
| | | |
| | | amountTrendChart.value.setOption(option) |
| | | } |
| | | |
| | | // æ¥è¯¢æä½ |
| | | const handleSearch = () => { |
| | | loadData() |
| | | } |
| | | |
| | | // éç½®æä½ |
| | | const handleReset = () => { |
| | | filterForm.dateRange = [] |
| | | filterForm.supplierName = '' |
| | | filterForm.productCategory = '' |
| | | loadData() |
| | | } |
| | | |
| | | // å¯¼åºæä½ |
| | | const handleExport = () => { |
| | | console.log('å¯¼åºæ°æ®') |
| | | } |
| | | |
| | | // çªå£å¤§å°æ¹åæ¶ï¼éæ°è°æ´å¾è¡¨å¤§å° |
| | | window.addEventListener('resize', () => { |
| | | if (categoryPieChart.value) categoryPieChart.value.resize() |
| | | if (amountTrendChart.value) amountTrendChart.value.resize() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inventory-statistics { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .filter-form { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .summary-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .summary-card { |
| | | text-align: center; |
| | | height: 100px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .summary-item { |
| | | width: 100%; |
| | | } |
| | | |
| | | .summary-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .summary-value.warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .summary-value.danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .chart-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-card { |
| | | height: 460px; |
| | | } |
| | | |
| | | .card-header { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .table_list { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .pagination { |
| | | text-align: right; |
| | | margin-top: 20px; |
| | | } |
| | | </style> |