<template>
|
<div class="stock-material-board">
|
<div class="page-header">
|
<h2>库存与物料看板</h2>
|
</div>
|
|
<!-- 搜索表单 -->
|
<el-card class="search-form-card" shadow="never">
|
<el-form :inline="true" :model="searchParams" class="search-form">
|
<el-form-item label="时间范围">
|
<el-date-picker
|
v-model="searchParams.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
:shortcuts="dateShortcuts"
|
/>
|
</el-form-item>
|
<el-form-item label="煤种">
|
<el-input v-model="searchParams.coalType" placeholder="请输入煤种" clearable />
|
</el-form-item>
|
<el-form-item label="车次编码">
|
<el-input v-model="searchParams.trainCode" placeholder="请输入车次编码" clearable />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleSearch" :loading="loading">查询</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleRefresh">刷新</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 统计卡片 -->
|
<div class="stats-cards">
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-download" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">今日入库总量</div>
|
<div class="card-value">{{ todayInStock.toFixed(2) }} 吨</div>
|
<div class="card-percentage" :class="{ positive: todayInStockPercentage > 0, negative: todayInStockPercentage < 0 }">
|
<i v-if="todayInStockPercentage > 0" class="el-icon-caret-top" />
|
<i v-else-if="todayInStockPercentage < 0" class="el-icon-caret-bottom" />
|
{{ Math.abs(todayInStockPercentage).toFixed(1) }}%
|
</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-upload" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">今日出库总量</div>
|
<div class="card-value">{{ todayOutStock.toFixed(2) }} 吨</div>
|
<div class="card-percentage" :class="{ positive: todayOutStockPercentage > 0, negative: todayOutStockPercentage < 0 }">
|
<i v-if="todayOutStockPercentage > 0" class="el-icon-caret-top" />
|
<i v-else-if="todayOutStockPercentage < 0" class="el-icon-caret-bottom" />
|
{{ Math.abs(todayOutStockPercentage).toFixed(1) }}%
|
</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-takeaway-box" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">今日领料总量</div>
|
<div class="card-value">{{ todayMaterialPick.toFixed(2) }} 吨</div>
|
<div class="card-percentage" :class="{ positive: todayMaterialPickPercentage > 0, negative: todayMaterialPickPercentage < 0 }">
|
<i v-if="todayMaterialPickPercentage > 0" class="el-icon-caret-top" />
|
<i v-else-if="todayMaterialPickPercentage < 0" class="el-icon-caret-bottom" />
|
{{ Math.abs(todayMaterialPickPercentage).toFixed(1) }}%
|
</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-shopping-cart-full" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">当前库存总量</div>
|
<div class="card-value">{{ currentTotalStock.toFixed(2) }} 吨</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 分类数据展示 -->
|
<el-card class="category-cards" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<span>分类数据统计</span>
|
</div>
|
</template>
|
|
<!-- 煤种分类 -->
|
<div class="category-section">
|
<h3 class="category-title"><i class="el-icon-menu"></i> 煤种分布</h3>
|
<div class="category-items">
|
<div v-for="item in coalTypeData" :key="item.name" class="category-item">
|
<span class="item-name">{{ item.name }}</span>
|
<div class="item-bar">
|
<div class="item-progress" :style="{ width: item.percentage + '%' }"></div>
|
</div>
|
<span class="item-value">{{ item.value.toFixed(2) }} 吨 ({{ item.percentage.toFixed(1) }}%)</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 产地分类 -->
|
<div class="category-section">
|
<h3 class="category-title"><i class="el-icon-location"></i> 产地分布</h3>
|
<div class="category-items">
|
<div v-for="item in originData" :key="item.name" class="category-item">
|
<span class="item-name">{{ item.name }}</span>
|
<div class="item-bar">
|
<div class="item-progress" :style="{ width: item.percentage + '%' }"></div>
|
</div>
|
<span class="item-value">{{ item.value.toFixed(2) }} 吨 ({{ item.percentage.toFixed(1) }}%)</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 热值分类 -->
|
<div class="category-section">
|
<h3 class="category-title"><i class="el-icon-fire"></i> 热值分布</h3>
|
<div class="category-items">
|
<div v-for="item in calorificData" :key="item.name" class="category-item">
|
<span class="item-name">{{ item.name }}</span>
|
<div class="item-bar">
|
<div class="item-progress" :style="{ width: item.percentage + '%' }"></div>
|
</div>
|
<span class="item-value">{{ item.value.toFixed(2) }} 吨 ({{ item.percentage.toFixed(1) }}%)</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 车次编码分类 -->
|
<div class="category-section">
|
<h3 class="category-title"><i class="el-icon-train"></i> 车次编码统计</h3>
|
<el-table :data="trainCodeData" style="width: 100%">
|
<el-table-column prop="trainCode" label="车次编码" width="160" />
|
<el-table-column prop="count" label="次数" width="100" align="right" />
|
<el-table-column prop="totalQuantity" label="总量(吨)" width="120" align="right">
|
<template #default="scope">{{ scope.row.totalQuantity.toFixed(2) }}</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</el-card>
|
|
<!-- 最近交易记录 -->
|
<el-card class="transactions-card" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<span>最近交易记录</span>
|
</div>
|
</template>
|
<el-table v-loading="loading" :data="recentTransactions" style="width: 100%">
|
<el-table-column prop="code" label="编码" width="160" />
|
<el-table-column prop="type" label="类型" width="80">
|
<template #default="scope">
|
<span class="type-tag"
|
:class="{
|
'type-in': scope.row.type === '入库',
|
'type-out': scope.row.type === '出库',
|
'type-pick': scope.row.type === '领料'
|
}">
|
{{ scope.row.type }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="coalType" label="煤种" width="100" />
|
<el-table-column prop="origin" label="产地" width="100" />
|
<el-table-column prop="calorificValue" label="热值" width="120" />
|
<el-table-column prop="quantity" label="数量(吨)" width="120" />
|
<el-table-column prop="trainCode" label="车次编码" width="120" />
|
<el-table-column prop="createTime" label="创建时间" width="180" />
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted } from 'vue'
|
import { ElMessage } from 'element-plus'
|
|
// 搜索参数
|
const searchParams = ref({
|
dateRange: [],
|
coalType: '',
|
trainCode: ''
|
})
|
|
// 日期快捷选项
|
const dateShortcuts = [
|
{
|
text: '今天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
return [start, end]
|
}
|
},
|
{
|
text: '昨天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24)
|
return [start, end]
|
}
|
},
|
{
|
text: '近7天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
return [start, end]
|
}
|
},
|
{
|
text: '近30天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
return [start, end]
|
}
|
},
|
{
|
text: '本月',
|
value: () => {
|
const end = new Date()
|
const start = new Date(end.getFullYear(), end.getMonth(), 1)
|
return [start, end]
|
}
|
}
|
]
|
|
// 统计数据
|
const todayInStock = ref(1250.75)
|
const todayInStockPercentage = ref(5.2)
|
const todayOutStock = ref(890.30)
|
const todayOutStockPercentage = ref(-2.1)
|
const todayMaterialPick = ref(320.45)
|
const todayMaterialPickPercentage = ref(10.3)
|
const currentTotalStock = ref(15680.95)
|
|
// 分类数据 - 煤种分布
|
const coalTypeData = ref([
|
{ name: '烟煤', value: 4500, percentage: 30.0 },
|
{ name: '无烟煤', value: 3200, percentage: 21.3 },
|
{ name: '褐煤', value: 2800, percentage: 18.7 },
|
{ name: '贫煤', value: 2500, percentage: 16.7 },
|
{ name: '瘦煤', value: 2680, percentage: 17.9 }
|
])
|
|
// 分类数据 - 产地分布
|
const originData = ref([
|
{ name: '山西', value: 5200, percentage: 33.1 },
|
{ name: '内蒙古', value: 3800, percentage: 24.2 },
|
{ name: '陕西', value: 2900, percentage: 18.5 },
|
{ name: '新疆', value: 2100, percentage: 13.4 },
|
{ name: '贵州', value: 1680, percentage: 10.7 }
|
])
|
|
// 分类数据 - 热值分布
|
const calorificData = ref([
|
{ name: '4000-4500', value: 2800, percentage: 17.9 },
|
{ name: '4500-5000', value: 4200, percentage: 26.8 },
|
{ name: '5000-5500', value: 5600, percentage: 35.7 },
|
{ name: '5500-6000', value: 2500, percentage: 16.0 },
|
{ name: '6000+', value: 580, percentage: 3.7 }
|
])
|
|
// 分类数据 - 车次编码统计
|
const trainCodeData = ref([
|
{ trainCode: 'C12345', count: 12, totalQuantity: 4200.5 },
|
{ trainCode: 'C12346', count: 8, totalQuantity: 2800.2 },
|
{ trainCode: 'C12347', count: 10, totalQuantity: 3500.0 },
|
{ trainCode: 'C12348', count: 7, totalQuantity: 2450.8 },
|
{ trainCode: 'C12349', count: 5, totalQuantity: 1800.3 }
|
])
|
|
// 模拟数据 - 最近交易记录
|
const recentTransactions = ref([
|
{ code: 'RK20230507001', type: '入库', coalType: '烟煤', origin: '山西', calorificValue: '5300大卡', quantity: 350.5, trainCode: 'C12345', createTime: '2023-05-07 09:30:15' },
|
{ code: 'CK20230507001', type: '出库', coalType: '无烟煤', origin: '内蒙古', calorificValue: '5800大卡', quantity: 280.2, trainCode: 'C12346', createTime: '2023-05-07 10:15:30' },
|
{ code: 'LL20230507001', type: '领料', coalType: '褐煤', origin: '新疆', calorificValue: '4200大卡', quantity: 120.8, trainCode: '', createTime: '2023-05-07 11:05:45' },
|
{ code: 'RK20230507002', type: '入库', coalType: '贫煤', origin: '陕西', calorificValue: '5100大卡', quantity: 400.0, trainCode: 'C12347', createTime: '2023-05-07 13:20:00' },
|
{ code: 'CK20230507002', type: '出库', coalType: '瘦煤', origin: '贵州', calorificValue: '5400大卡', quantity: 310.5, trainCode: 'C12348', createTime: '2023-05-07 14:45:15' },
|
{ code: 'LL20230507002', type: '领料', coalType: '烟煤', origin: '山西', calorificValue: '5300大卡', quantity: 200.0, trainCode: '', createTime: '2023-05-07 15:30:30' }
|
])
|
|
// 加载状态
|
const loading = ref(false)
|
|
// 查询数据
|
const handleSearch = async () => {
|
try {
|
loading.value = true
|
console.log('搜索参数:', searchParams.value)
|
|
// 模拟API请求延迟
|
await new Promise(resolve => setTimeout(resolve, 800))
|
|
// 模拟数据更新
|
todayInStock.value = 1000 + Math.random() * 500
|
todayInStockPercentage.value = -10 + Math.random() * 20
|
todayOutStock.value = 800 + Math.random() * 400
|
todayOutStockPercentage.value = -10 + Math.random() * 20
|
todayMaterialPick.value = 200 + Math.random() * 200
|
todayMaterialPickPercentage.value = -10 + Math.random() * 20
|
currentTotalStock.value = 15000 + Math.random() * 3000
|
|
// 更新分类数据
|
coalTypeData.value.forEach(item => {
|
item.value = 2000 + Math.random() * 3000
|
})
|
|
originData.value.forEach(item => {
|
item.value = 1500 + Math.random() * 4000
|
})
|
|
calorificData.value.forEach(item => {
|
item.value = 500 + Math.random() * 5000
|
})
|
|
trainCodeData.value.forEach(item => {
|
item.count = 3 + Math.floor(Math.random() * 15)
|
item.totalQuantity = 1000 + Math.random() * 4000
|
})
|
|
// 重新计算百分比
|
const calculatePercentages = (data) => {
|
const total = data.reduce((sum, item) => sum + item.value, 0)
|
data.forEach(item => {
|
item.percentage = total > 0 ? (item.value / total) * 100 : 0
|
})
|
}
|
|
calculatePercentages(coalTypeData.value)
|
calculatePercentages(originData.value)
|
calculatePercentages(calorificData.value)
|
|
ElMessage.success('查询成功')
|
} catch (error) {
|
console.error('查询失败:', error)
|
ElMessage.error('查询失败,请稍后重试')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 重置搜索参数
|
const handleReset = () => {
|
searchParams.value = {
|
dateRange: [],
|
coalType: '',
|
trainCode: ''
|
}
|
}
|
|
// 刷新数据
|
const handleRefresh = () => {
|
ElMessage.info('正在刷新数据...')
|
handleSearch()
|
}
|
|
// 自动生成编码函数
|
const generateCode = (type) => {
|
const prefix = type === '入库' ? 'RK' : type === '出库' ? 'CK' : 'LL'
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')
|
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0')
|
return `${prefix}${date}${random}`
|
}
|
|
// 初始化页面
|
onMounted(() => {
|
// 默认查询近7天数据
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
searchParams.value.dateRange = [start, end]
|
|
// 初始加载数据
|
handleSearch()
|
})
|
</script>
|
|
<style scoped>
|
.stock-material-board {
|
padding: 20px;
|
}
|
|
.page-header {
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin-bottom: 15px;
|
font-size: 18px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.search-form-card {
|
margin-bottom: 20px;
|
}
|
|
.search-form {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 16px;
|
}
|
|
.stats-cards {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
gap: 16px;
|
margin-bottom: 24px;
|
}
|
|
.stat-card {
|
display: flex;
|
padding: 20px;
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
transition: transform 0.3s, box-shadow 0.3s;
|
}
|
|
.stat-card:hover {
|
transform: translateY(-2px);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
}
|
|
.card-icon {
|
width: 48px;
|
height: 48px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 50%;
|
font-size: 24px;
|
margin-right: 16px;
|
}
|
|
.stat-card:nth-child(1) .card-icon {
|
background: #F6FFED;
|
color: #52C41A;
|
}
|
|
.stat-card:nth-child(2) .card-icon {
|
background: #FFF7E6;
|
color: #FA8C16;
|
}
|
|
.stat-card:nth-child(3) .card-icon {
|
background: #FFF2F0;
|
color: #FF4D4F;
|
}
|
|
.stat-card:nth-child(4) .card-icon {
|
background: #E6F7FF;
|
color: #1890FF;
|
}
|
|
.card-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 8px;
|
}
|
|
.card-value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #303133;
|
margin-bottom: 4px;
|
}
|
|
.card-percentage {
|
font-size: 12px;
|
}
|
|
.card-percentage.positive {
|
color: #52C41A;
|
}
|
|
.card-percentage.negative {
|
color: #FF4D4F;
|
}
|
|
.category-cards {
|
margin-bottom: 24px;
|
}
|
|
.category-section {
|
margin-bottom: 24px;
|
padding-bottom: 20px;
|
border-bottom: 1px solid #EBEEF5;
|
}
|
|
.category-section:last-child {
|
margin-bottom: 0;
|
padding-bottom: 0;
|
border-bottom: none;
|
}
|
|
.category-title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
margin-bottom: 16px;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.category-items {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
}
|
|
.category-item {
|
display: flex;
|
align-items: center;
|
gap: 16px;
|
}
|
|
.item-name {
|
width: 100px;
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.item-bar {
|
flex: 1;
|
height: 8px;
|
background-color: #F5F7FA;
|
border-radius: 4px;
|
overflow: hidden;
|
}
|
|
.item-progress {
|
height: 100%;
|
background-color: #409EFF;
|
border-radius: 4px;
|
transition: width 0.3s ease;
|
}
|
|
.item-value {
|
width: 140px;
|
text-align: right;
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.transactions-card {
|
background: #fff;
|
border-radius: 8px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 16px 20px;
|
border-bottom: 1px solid #EBEEF5;
|
}
|
|
.card-header span {
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.type-tag {
|
padding: 4px 8px;
|
border-radius: 4px;
|
font-size: 12px;
|
}
|
|
.type-in {
|
background: #F6FFED;
|
color: #52C41A;
|
border: 1px solid #B7EB8F;
|
}
|
|
.type-out {
|
background: #FFF7E6;
|
color: #FA8C16;
|
border: 1px solid #FFD591;
|
}
|
|
.type-pick {
|
background: #FFF2F0;
|
color: #FF4D4F;
|
border: 1px solid #FFCCC7;
|
}
|
|
/* 响应式设计 */
|
@media screen and (max-width: 1200px) {
|
.stats-cards {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
}
|
|
@media screen and (max-width: 768px) {
|
.stats-cards {
|
grid-template-columns: 1fr;
|
}
|
|
.search-form {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.search-form .el-form-item {
|
width: 100%;
|
}
|
}
|
</style>
|