| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <el-form :model="searchForm" :inline="true" class="search-form"> |
| | | <el-form-item label="æ¶é´èå´ï¼"> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="产å大类ï¼"> |
| | | <el-tree-select |
| | | v-model="searchForm.productCategory" |
| | | placeholder="è¯·éæ©ååç±»å«" |
| | | clearable |
| | | check-strictly |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch" :loading="loading"> |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch"> |
| | | éç½® |
| | | </el-button> |
| | | <el-button type="info" @click="exportReport"> |
| | | <el-icon><Download /></el-icon> |
| | | å¯¼åº |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <!-- æ¥è¡¨å
容 --> |
| | | <el-card class="report-content" shadow="never"> |
| | | <!-- éè´ä¸å¡æ±æ»è¡¨ --> |
| | | <div class="report-section"> |
| | | <div class="section-header"> |
| | | <h3>éè´ä¸å¡æ±æ»è¡¨</h3> |
| | | <div class="summary-stats"> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">éè´æ»é¢ï¼</span> |
| | | <span class="stat-value">Â¥{{ businessSummaryStats.totalAmount.toLocaleString() }}</span> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">ååç§ç±»ï¼</span> |
| | | <span class="stat-value">{{ businessSummaryStats.productTypes }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | :table-data="businessSummaryData" |
| | | :column="tableColumns" |
| | | :table-loading="loading" |
| | | :is-selection="false" |
| | | :border="true" |
| | | :is-show-pagination="true" |
| | | :page="page" |
| | | @pagination="handlePagination" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Download } from '@element-plus/icons-vue' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { procurementBusinessSummaryListPage } from '@/api/procurementManagement/procurementReport' |
| | | import { productTreeList } from '@/api/basicData/product' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | dateRange: [], |
| | | productCategory: '' |
| | | }) |
| | | |
| | | // 产åç±»å«æ é项 |
| | | const productOptions = ref([]) |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const businessSummaryStats = ref({ |
| | | totalAmount: 0, |
| | | productTypes: 0 |
| | | }) |
| | | |
| | | // è¡¨æ ¼åé
ç½®ï¼æ ¹æ®åç«¯åæ®µå®ä¹ï¼ |
| | | const tableColumns = ref([ |
| | | { |
| | | label: '产å大类', |
| | | prop: 'productCategory', |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: 'è§æ ¼åå·', |
| | | prop: 'specificationModel', |
| | | width: 180 |
| | | }, |
| | | { |
| | | label: 'éè´æ°é', |
| | | prop: 'purchaseNum', |
| | | width: 120, |
| | | formatData: (val) => { |
| | | return val ? parseFloat(val).toLocaleString() : '0' |
| | | } |
| | | }, |
| | | { |
| | | label: 'éè´éé¢', |
| | | prop: 'purchaseAmount', |
| | | width: 140, |
| | | formatData: (val) => { |
| | | return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00' |
| | | } |
| | | }, |
| | | { |
| | | label: 'éè´æ¬¡æ°', |
| | | prop: 'purchaseTimes', |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: 'å¹³ååä»·', |
| | | prop: 'averagePrice', |
| | | width: 120, |
| | | formatData: (val) => { |
| | | return val ? `Â¥${parseFloat(val).toFixed(2)}` : 'Â¥0.00' |
| | | } |
| | | }, |
| | | { |
| | | label: 'ä¾åºååç§°', |
| | | prop: 'supplierName', |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: 'å½å
¥æ¥æ', |
| | | prop: 'entryDate', |
| | | width: 120 |
| | | } |
| | | ]) |
| | | |
| | | // éè´ä¸å¡æ±æ»è¡¨æ°æ® |
| | | const businessSummaryData = ref([]) |
| | | |
| | | // å页忰ï¼å端è¿åï¼total/size/current/pagesï¼ |
| | | const page = reactive({ |
| | | total: 0, |
| | | current: 1, |
| | | size: 50, |
| | | }) |
| | | |
| | | // 转æ¢äº§åæ æ°æ®ï¼å° id æ¹ä¸º value |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, |
| | | } |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children) |
| | | } |
| | | return newItem |
| | | }) |
| | | } |
| | | |
| | | // è·å产åç±»å«æ æ°æ® |
| | | const getProductOptions = () => { |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res) |
| | | }).catch((error) => { |
| | | console.error('è·åäº§åæ å¤±è´¥:', error) |
| | | ElMessage.error('è·å产åç±»å«å¤±è´¥') |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ® id æ¥æ¾äº§åç±»å«åç§° |
| | | const findNodeLabelById = (nodes, id) => { |
| | | if (!id) return null |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === id) { |
| | | return nodes[i].label |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const found = findNodeLabelById(nodes[i].children, id) |
| | | if (found) return found |
| | | } |
| | | } |
| | | return null |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleSearch = async () => { |
| | | try { |
| | | loading.value = true |
| | | const params = {} |
| | | |
| | | // æ¶é´èå´ |
| | | if (searchForm.dateRange && searchForm.dateRange.length === 2) { |
| | | params.entryDateStart = searchForm.dateRange[0] |
| | | params.entryDateEnd = searchForm.dateRange[1] |
| | | } |
| | | |
| | | // 产åç±»å« |
| | | if (searchForm.productCategory) { |
| | | const categoryName = findNodeLabelById(productOptions.value, searchForm.productCategory) |
| | | if (categoryName) { |
| | | params.productCategory = categoryName |
| | | } |
| | | } |
| | | |
| | | // å页忰 |
| | | params.current = page.current |
| | | params.size = page.size |
| | | |
| | | const res = await procurementBusinessSummaryListPage(params) |
| | | if (res && res.data) { |
| | | // å
¼å®¹å端å¯è½ç´æ¥è¿åæ°ç»/æè¿åå页对象 |
| | | businessSummaryData.value = Array.isArray(res.data) ? res.data : (res.data.records || []) |
| | | |
| | | if (!Array.isArray(res.data)) { |
| | | page.total = Number(res.data.total ?? 0) |
| | | page.current = Number(res.data.current ?? page.current) |
| | | page.size = Number(res.data.size ?? page.size) |
| | | } |
| | | |
| | | // 计ç®ç»è®¡æ°æ® |
| | | if (businessSummaryData.value.length > 0) { |
| | | businessSummaryStats.value.totalAmount = businessSummaryData.value.reduce((sum, item) => { |
| | | return sum + (parseFloat(item.purchaseAmount) || 0) |
| | | }, 0) |
| | | businessSummaryStats.value.productTypes = new Set(businessSummaryData.value.map(item => item.productCategory)).size |
| | | } else { |
| | | businessSummaryStats.value = { |
| | | totalAmount: 0, |
| | | productTypes: 0 |
| | | } |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('æ¥è¯¢å¤±è´¥:', error) |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // 翻页/忢æ¯é¡µæ¡æ° |
| | | const handlePagination = ({ page: current, limit }) => { |
| | | page.current = current |
| | | page.size = limit |
| | | handleSearch() |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | dateRange: [], |
| | | productCategory: '' |
| | | }) |
| | | page.current = 1 |
| | | handleSearch() |
| | | } |
| | | |
| | | const exportReport = () => { |
| | | ElMessage.success('导åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | // åå§å产åç±»å«æ |
| | | getProductOptions() |
| | | |
| | | // 设置é»è®¤æ¶é´èå´ä¸ºæè¿30天 |
| | | const endDate = new Date() |
| | | const startDate = new Date() |
| | | startDate.setDate(startDate.getDate() - 30) |
| | | |
| | | searchForm.dateRange = [ |
| | | startDate.toISOString().split('T')[0], |
| | | endDate.toISOString().split('T')[0] |
| | | ] |
| | | |
| | | // åå§å è½½æ°æ® |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 10px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0 0 10px 0; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | margin: 0; |
| | | font-size: 16px; |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .report-content { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .report-section { |
| | | min-height: 400px; |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 2px solid #e4e7ed; |
| | | } |
| | | |
| | | .section-header h3 { |
| | | margin: 0; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .summary-stats { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .stat-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .delay-text { |
| | | color: #F56C6C; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | |
| | | </style> |