已添加3个文件
已修改6个文件
1847 ■■■■■ 文件已修改
src/views/equipment/management/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipment/management/mould/managementDialog.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/components/ProductionDialog.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/ProductionDetailsTable.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionControl/intelligentScheduling/index.vue 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionControl/reportAnalysis/index.vue 688 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionControl/stockMaterialBoard/index.vue 636 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesOutbound/components/formDia.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipment/management/index.vue
@@ -197,7 +197,7 @@
};
// å“åº”式状态管理 - ä½¿ç”¨è§£æž„和默认值
const initFormState = () => ({ consumables: false });
const initFormState = () => ({ isConsumables: false });
const state = reactive({
  form: initFormState(),
@@ -254,8 +254,8 @@
    columns: [
      { prop: "equipmentNo", label: "设备编号", minWidth: 100 },
      { prop: "equipmentName", label: "设备名称", minWidth: 100 },
      { prop: "consumables", label: "耗材",
        formatter: (row) => (row.consumables ? "是" : "否"), minWidth: 100 },
      { prop: "isConsumables", label: "耗材",
        formatter: (row) => (row.isConsumables ? "是" : "否"), minWidth: 100 },
      { prop: "quantity", label: "总数量", minWidth: 100 },
      { prop: "usedNo", label: "已使用数量", minWidth: 100 },
      { prop: "specification", label: "规格型号", minWidth: 100 },
@@ -387,7 +387,7 @@
const resetState = () => {
  form.value = {
    consumables: false,
    isConsumables: false,
  };
  addOrEdit.value = "add";
  loading.value = true;
@@ -412,7 +412,7 @@
const handleAdd = () => {
  addOrEdit.value = "add";
  form.value = {
    consumables: false,
    isConsumables: false,
  };
  title.value = `新增${currentTabConfig.value.label}`;
  // é€šç”¨çš„
src/views/equipment/management/mould/managementDialog.vue
@@ -36,8 +36,8 @@
            </el-form-item>
          </el-col>
          <el-col :span="11">
            <el-form-item label="是否为耗材" prop="consumables">
              <el-select v-model="formData.consumables" placeholder="请选择是否为耗材类型" :disabled="isViewMode">
            <el-form-item label="是否为耗材" prop="isConsumables">
              <el-select v-model="formData.isConsumables" placeholder="请选择是否为耗材类型" :disabled="isViewMode">
                <el-option label="是" :value="true" />
                <el-option label="否" :value="false" />
              </el-select>
@@ -233,7 +233,7 @@
  equipmentName: [
    { required: true, message: "请输入供货商名称", trigger: "blur" },
  ],
  consumables: [
  isConsumables: [
    { required: true, message: "请选择是否为耗材", trigger: "change" },
  ],
});
src/views/procureMent/components/ProductionDialog.vue
@@ -127,6 +127,16 @@
            </template>
          </el-input>
        </el-form-item>
        <el-form-item label="车牌号" prop="licensePlate">
          <!-- é»˜è®¤ä¸º0 -->
          <el-input
              :precision="2"
              v-model.number="form.licensePlate"
              placeholder="请输入车牌号"
              :disabled="isViewMode"
          >
          </el-input>
        </el-form-item>
        <el-form-item label="运费" prop="freight">
          <!-- é»˜è®¤ä¸º0 -->
          <el-input
src/views/procureMent/index.vue
@@ -183,6 +183,7 @@
  { prop: "purchaseQuantity", label: "采购数量", minWidth: 100 },
  { prop: "priceIncludingTax", label: "单价(含税)", minWidth: 150 },
  { prop: "totalPriceIncludingTax", label: "总价(含税)", minWidth: 100 },
  { prop: "licensePlate", label: "车牌号", minWidth: 100 },
  { prop: "freight", label: "运费", minWidth: 100 },
  { prop: "taxRate", label: "税率", minWidth: 100 },
  { prop: "priceExcludingTax", label: "不含税单价", minWidth: 100 },
src/views/production/components/ProductionDetailsTable.vue
@@ -102,11 +102,11 @@
      </template>
    </el-table-column>
    <el-table-column label="采购单价" min-width="120">
    <el-table-column label="采购总价" min-width="120">
      <template #default="{ row, $index }">
        <el-input
            v-model="row.purchasePrice"
            placeholder="请输入采购单价"
            placeholder="请输入采购总价"
            type="number"
            @input="handleInput('purchasePrice', $index, $event)"
            :disabled="isViewMode"
src/views/productionControl/intelligentScheduling/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,488 @@
<template>
  <div class="intelligent-scheduling">
    <!-- æœç´¢è¡¨å• -->
    <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-select v-model="searchParams.status" placeholder="请选择排产状态" clearable style="width: 200px">
            <el-option label="全部" value="" />
            <el-option label="待排产" value="pending" />
            <el-option label="排产中" value="processing" />
            <el-option label="已完成" value="completed" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch" :loading="loading">查询</el-button>
          <el-button @click="handleReset">重置</el-button>
          <el-button type="success" @click="handleRandomScheduling">随机排产</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-s-order" />
        </div>
        <div class="card-content">
          <div class="card-title">总排产量</div>
          <div class="card-value">{{ totalScheduledQuantity.toFixed(2) }} å¨</div>
        </div>
      </div>
      <div class="stat-card">
        <div class="card-icon">
          <i class="el-icon-check" />
        </div>
        <div class="card-content">
          <div class="card-title">已完成排产</div>
          <div class="card-value">{{ completedScheduledQuantity.toFixed(2) }} å¨</div>
        </div>
      </div>
      <div class="stat-card">
        <div class="card-icon">
          <i class="el-icon-time" />
        </div>
        <div class="card-content">
          <div class="card-title">待排产</div>
          <div class="card-value">{{ pendingScheduledQuantity.toFixed(2) }} å¨</div>
        </div>
      </div>
      <div class="stat-card">
        <div class="card-icon">
          <i class="el-icon-warning" />
        </div>
        <div class="card-content">
          <div class="card-title">库存预警</div>
          <div class="card-value">{{ stockWarningCount }} é¡¹</div>
        </div>
      </div>
    </div>
    <!-- åº“存原料列表 -->
    <el-card class="stock-materials-card" shadow="never">
      <template #header>
        <div class="card-header">
          <span>库存原料</span>
        </div>
      </template>
      <el-table v-loading="loading" :data="stockMaterials" style="width: 100%">
        <el-table-column prop="id" label="序号" width="80" type="index" />
        <el-table-column prop="coalType" label="煤种" width="120" />
        <el-table-column prop="origin" label="产地" width="120" />
        <el-table-column prop="calorificValue" label="热值" width="120" />
        <el-table-column prop="currentStock" label="当前库存(吨)" width="150" align="right">
          <template #default="scope">
            <span :class="{ 'stock-warning': scope.row.currentStock < scope.row.minStock }">
              {{ scope.row.currentStock.toFixed(2) }}
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="minStock" label="最低库存(吨)" width="150" align="right">
          <template #default="scope">{{ scope.row.minStock.toFixed(2) }}</template>
        </el-table-column>
        <el-table-column prop="unit" label="单位" width="80" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
      </el-table>
    </el-card>
    <!-- æŽ’产结果列表 -->
    <el-card class="scheduling-results-card" shadow="never">
      <template #header>
        <div class="card-header">
          <span>排产结果</span>
        </div>
      </template>
      <el-table v-loading="loading" :data="schedulingResults" style="width: 100%">
        <el-table-column prop="code" label="排产编码" width="180" />
        <el-table-column prop="productionLine" label="生产线" width="120" />
        <el-table-column prop="coalType" label="煤种" width="120" />
        <el-table-column prop="quantity" label="排产数量(吨)" width="150" align="right">
          <template #default="scope">{{ scope.row.quantity.toFixed(2) }}</template>
        </el-table-column>
        <el-table-column prop="scheduleTime" label="排产时间" width="180" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag
              :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'"
              size="small"
            >
              {{ scope.row.status === 'completed' ? '已完成' : scope.row.status === 'processing' ? '排产中' : '待排产' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注" width="200" />
      </el-table>
    </el-card>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
// æœç´¢å‚æ•°
const searchParams = ref({
  dateRange: [],
  coalType: '',
  status: ''
})
// æ—¥æœŸå¿«æ·é€‰é¡¹
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 totalScheduledQuantity = ref(0)
const completedScheduledQuantity = ref(0)
const pendingScheduledQuantity = ref(0)
const stockWarningCount = ref(0)
// åº“存原料数据
const stockMaterials = ref([
  { id: 1, coalType: '烟煤', origin: '山西', calorificValue: '5300大卡', currentStock: 1200.5, minStock: 500, unit: '吨', updateTime: '2023-05-07 09:30:15' },
  { id: 2, coalType: '无烟煤', origin: '内蒙古', calorificValue: '5800大卡', currentStock: 800.2, minStock: 400, unit: '吨', updateTime: '2023-05-07 10:15:30' },
  { id: 3, coalType: '褐煤', origin: '新疆', calorificValue: '4200大卡', currentStock: 350.8, minStock: 400, unit: '吨', updateTime: '2023-05-07 11:05:45' },
  { id: 4, coalType: '贫煤', origin: '陕西', calorificValue: '5100大卡', currentStock: 900.0, minStock: 300, unit: '吨', updateTime: '2023-05-07 13:20:00' },
  { id: 5, coalType: '瘦煤', origin: '贵州', calorificValue: '5400大卡', currentStock: 650.5, minStock: 350, unit: '吨', updateTime: '2023-05-07 14:45:15' }
])
// æŽ’产结果数据
const schedulingResults = ref([
  { code: 'PS20230507001', productionLine: '生产线1', coalType: '烟煤', quantity: 200.5, scheduleTime: '2023-05-07 09:30:15', status: 'completed', remark: '按计划完成' },
  { code: 'PS20230507002', productionLine: '生产线2', coalType: '无烟煤', quantity: 150.2, scheduleTime: '2023-05-07 10:15:30', status: 'processing', remark: '正在进行中' },
  { code: 'PS20230507003', productionLine: '生产线3', coalType: '贫煤', quantity: 180.0, scheduleTime: '2023-05-07 11:05:45', status: 'pending', remark: '等待排产' }
])
// åŠ è½½çŠ¶æ€
const loading = ref(false)
// è®¡ç®—统计数据
const calculateStats = () => {
  // è®¡ç®—总排产量
  totalScheduledQuantity.value = schedulingResults.value.reduce((sum, item) => sum + item.quantity, 0)
  // è®¡ç®—已完成排产
  completedScheduledQuantity.value = schedulingResults.value
    .filter(item => item.status === 'completed')
    .reduce((sum, item) => sum + item.quantity, 0)
  // è®¡ç®—待排产
  pendingScheduledQuantity.value = schedulingResults.value
    .filter(item => item.status === 'pending')
    .reduce((sum, item) => sum + item.quantity, 0)
  // è®¡ç®—库存预警数量
  stockWarningCount.value = stockMaterials.value.filter(item => item.currentStock < item.minStock).length
}
// ç”ŸæˆæŽ’产编码
const generateSchedulingCode = () => {
  const date = new Date()
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0')
  return `PS${year}${month}${day}${random}`
}
// éšæœºæŽ’产
const handleRandomScheduling = async () => {
  try {
    loading.value = true
    // æ¨¡æ‹ŸAPI请求延迟
    await new Promise(resolve => setTimeout(resolve, 1000))
    // èŽ·å–æœ‰åº“å­˜çš„åŽŸæ–™
    const availableMaterials = stockMaterials.value.filter(item => item.currentStock > 0)
    if (availableMaterials.length === 0) {
      ElMessage.warning('没有可用的库存原料')
      return
    }
    // éšæœºé€‰æ‹©åŽŸæ–™
    const randomMaterial = availableMaterials[Math.floor(Math.random() * availableMaterials.length)]
    // éšæœºç”ŸæˆæŽ’产数量(不超过库存的50%)
    const maxQuantity = randomMaterial.currentStock * 0.5
    const quantity = Math.max(50, Math.random() * maxQuantity) // è‡³å°‘排产50吨
    // éšæœºé€‰æ‹©ç”Ÿäº§çº¿
    const productionLines = ['生产线1', '生产线2', '生产线3']
    const randomLine = productionLines[Math.floor(Math.random() * productionLines.length)]
    // ç”Ÿæˆæ–°çš„æŽ’产计划
    const newScheduling = {
      code: generateSchedulingCode(),
      productionLine: randomLine,
      coalType: randomMaterial.coalType,
      quantity: quantity,
      scheduleTime: new Date().toLocaleString('zh-CN'),
      status: 'pending',
      remark: '随机排产生成'
    }
    // æ·»åŠ åˆ°æŽ’äº§ç»“æžœåˆ—è¡¨
    schedulingResults.value.unshift(newScheduling)
    // æ›´æ–°åº“存数量
    randomMaterial.currentStock -= quantity
    // æ›´æ–°ç»Ÿè®¡æ•°æ®
    calculateStats()
    ElMessage.success('随机排产成功')
  } catch (error) {
    console.error('随机排产失败:', error)
    ElMessage.error('随机排产失败,请稍后重试')
  } finally {
    loading.value = false
  }
}
// æŸ¥è¯¢æ•°æ®
const handleSearch = async () => {
  try {
    loading.value = true
    // æ¨¡æ‹ŸAPI请求延迟
    await new Promise(resolve => setTimeout(resolve, 800))
    // è¿™é‡Œå¯ä»¥æ ¹æ®æœç´¢æ¡ä»¶è¿‡æ»¤æ•°æ®
    // å®žé™…应用中应该调用API获取数据
    ElMessage.success('查询成功')
  } catch (error) {
    console.error('查询失败:', error)
    ElMessage.error('查询失败,请稍后重试')
  } finally {
    loading.value = false
  }
}
// é‡ç½®æœç´¢å‚æ•°
const handleReset = () => {
  searchParams.value = {
    dateRange: [],
    coalType: '',
    status: ''
  }
  // é‡ç½®åŽé‡æ–°æŸ¥è¯¢
  handleSearch()
}
// åˆå§‹åŒ–页面
onMounted(() => {
  // é»˜è®¤æŸ¥è¯¢è¿‘7天数据
  const end = new Date()
  const start = new Date()
  start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
  searchParams.value.dateRange = [start, end]
  // è®¡ç®—统计数据
  calculateStats()
  // åˆå§‹åŠ è½½æ•°æ®
  handleSearch()
})
</script>
<style scoped>
.intelligent-scheduling {
  padding: 20px;
}
.page-header {
  margin-bottom: 20px;
}
.page-header h2 {
  font-size: 18px;
  font-weight: 600;
  color: #303133;
}
.search-form-card {
  margin-bottom: 20px;
}
.search-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
.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: #ECF5FF;
  color: #409EFF;
}
.stat-card:nth-child(2) .card-icon {
  background: #F6FFED;
  color: #52C41A;
}
.stat-card:nth-child(3) .card-icon {
  background: #FFF7E6;
  color: #FAAD14;
}
.stat-card:nth-child(4) .card-icon {
  background: #FFF1F0;
  color: #F5222D;
}
.card-content {
  flex: 1;
}
.card-title {
  font-size: 14px;
  color: #606266;
  margin-bottom: 8px;
}
.card-value {
  font-size: 24px;
  font-weight: 600;
  color: #303133;
}
.stock-materials-card,
.scheduling-results-card {
  margin-bottom: 24px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  border-bottom: 1px solid #EBEEF5;
}
.card-header span {
  font-weight: 600;
  color: #303133;
}
.stock-warning {
  color: #F5222D;
  font-weight: 500;
}
/* å“åº”式布局 */
@media (max-width: 768px) {
  .intelligent-scheduling {
    padding: 10px;
  }
  .stats-cards {
    grid-template-columns: 1fr;
  }
  .search-form {
    flex-direction: column;
    align-items: stretch;
  }
  .search-form .el-form-item {
    margin-right: 0;
    margin-bottom: 10px;
  }
}
</style>
src/views/productionControl/reportAnalysis/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,688 @@
<template>
    <div class="coal-blending-efficiency">
        <div class="page-header">
            <div class="search-form">
                <el-form :inline="true" :model="searchParams" class="demo-form-inline">
                    <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>
                        <el-button type="primary" @click="handleSearch" :loading="loading">查询</el-button>
                        <el-button @click="handleReset">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </div>
        <!-- ç»Ÿè®¡å¡ç‰‡åŒºåŸŸ -->
        <div class="stats-cards">
            <div class="stat-card">
                <div class="card-icon">
                    <i class="el-icon-s-operation"></i>
                </div>
                <div class="card-content">
                    <div class="card-title">单位热值耗煤量</div>
                    <div class="card-value">{{ unitHeatCoalConsumption.toFixed(2) }} kg/GJ</div>
                    <div class="card-desc">越低越好</div>
                </div>
            </div>
            <div class="stat-card">
                <div class="card-icon">
                    <i class="el-icon-check-circle"></i>
                </div>
                <div class="card-content">
                    <div class="card-title">整体达标率</div>
                    <div class="card-value">{{ overallComplianceRate.toFixed(2) }}%</div>
                    <div class="card-desc">发热量≥5000大卡</div>
                </div>
            </div>
            <div class="stat-card">
                <div class="card-icon">
                    <i class="el-icon-data-line"></i>
                </div>
                <div class="card-content">
                    <div class="card-title">平均发热量</div>
                    <div class="card-value">{{ averageCalorificValue.toFixed(0) }} å¤§å¡</div>
                    <div class="card-desc">越高越好</div>
                </div>
            </div>
            <div class="stat-card">
                <div class="card-icon">
                    <i class="el-icon-files"></i>
                </div>
                <div class="card-content">
                    <div class="card-title">配方使用频次</div>
                    <div class="card-value">{{ totalBatches }} æ¬¡</div>
                    <div class="card-desc">时间范围内</div>
                </div>
            </div>
            <div class="stat-card">
                <div class="card-icon">
                    <i class="el-icon-s-data"></i>
                </div>
                <div class="card-content">
                    <div class="card-title">总批次</div>
                    <div class="card-value">{{ totalBatches }}</div>
                    <div class="card-desc">时间范围内</div>
                </div>
            </div>
        </div>
        <!-- å›¾è¡¨åŒºåŸŸ -->
        <div class="charts-container">
            <!-- è¾¾æ ‡çŽ‡è¶‹åŠ¿å›¾ -->
            <el-card class="chart-card">
                <template #header>
                    <div class="card-header">
                        <span>达标率趋势</span>
                    </div>
                </template>
                <div class="chart-wrapper">
                    <Echarts
                        ref="complianceChartRef"
                        :options="complianceTrendOptions"
                        :chartStyle="{ height: '300px', width: '100%' }"
                    />
                </div>
            </el-card>
            <!-- ç…¤ç§å‘热量对比 -->
            <el-card class="chart-card">
                <template #header>
                    <div class="card-header">
                        <span>煤种发热量对比</span>
                    </div>
                </template>
                <div class="chart-wrapper">
                    <Echarts
                        ref="calorificChartRef"
                        :options="calorificValueOptions"
                        :chartStyle="{ height: '300px', width: '100%' }"
                    />
                </div>
            </el-card>
            <!-- é…æ–¹ä½¿ç”¨é¢‘次 -->
            <el-card class="chart-card">
                <template #header>
                    <div class="card-header">
                        <span>配方使用频次</span>
                    </div>
                </template>
                <div class="chart-wrapper">
                    <Echarts
                        ref="recipeChartRef"
                        :options="recipeFrequencyOptions"
                        :chartStyle="{ height: '300px', width: '100%' }"
                    />
                </div>
            </el-card>
            <!-- åŠ å·¥å¾—çŽ‡åˆ†æž -->
            <el-card class="chart-card">
                <template #header>
                    <div class="card-header">
                        <span>加工得率分析</span>
                    </div>
                </template>
                <div class="chart-wrapper">
                    <Echarts
                        ref="yieldChartRef"
                        :options="processingYieldOptions"
                        :chartStyle="{ height: '300px', width: '100%' }"
                    />
                </div>
            </el-card>
            <!-- æˆæœ¬ç»“构图谱 -->
            <el-card class="chart-card cost-structure">
                <template #header>
                    <div class="card-header">
                        <span>成本结构图谱</span>
                    </div>
                </template>
                <div class="chart-wrapper">
                    <Echarts
                        ref="costChartRef"
                        :options="costStructureOptions"
                        :chartStyle="{ height: '300px', width: '100%' }"
                    />
                </div>
            </el-card>
        </div>
    </div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
import { ElMessage } from 'element-plus'
// æœç´¢å‚æ•°
const searchParams = ref({
    dateRange: []
})
// æ—¥æœŸå¿«æ·é€‰é¡¹
const dateShortcuts = [
    {
        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 unitHeatCoalConsumption = ref(32.5)
const overallComplianceRate = ref(85.7)
const averageCalorificValue = ref(5260)
const totalBatches = ref(48)
// æ¨¡æ‹Ÿæ•°æ®
const mockComplianceData = [
    { date: '2023-06-01', rate: 82 },
    { date: '2023-06-02', rate: 85 },
    { date: '2023-06-03', rate: 88 },
    { date: '2023-06-04', rate: 84 },
    { date: '2023-06-05', rate: 87 },
    { date: '2023-06-06', rate: 90 },
    { date: '2023-06-07', rate: 86 },
    { date: '2023-06-08', rate: 89 },
    { date: '2023-06-09', rate: 92 },
    { date: '2023-06-10', rate: 88 }
]
const mockCalorificData = [
    { name: '烟煤', value: 5800 },
    { name: '无烟煤', value: 6200 },
    { name: '褐煤', value: 4500 },
    { name: '贫煤', value: 5300 },
    { name: '瘦煤', value: 5500 }
]
const mockRecipeData = [
    { name: '配方A (烟煤60%+无烟煤40%)', value: 18 },
    { name: '配方B (烟煤70%+褐煤30%)', value: 12 },
    { name: '配方C (无烟煤50%+贫煤50%)', value: 8 },
    { name: '配方D (烟煤50%+瘦煤50%)', value: 6 },
    { name: '其他配方', value: 4 }
]
const mockYieldData = [
    { name: '烟煤', yield: 85, efficiency: 92 },
    { name: '无烟煤', yield: 88, efficiency: 89 },
    { name: '褐煤', yield: 75, efficiency: 78 },
    { name: '贫煤', yield: 82, efficiency: 85 },
    { name: '瘦煤', yield: 80, efficiency: 87 }
]
const mockCostData = [
    { name: '耗材', value: 45 },
    { name: '能耗', value: 30 },
    { name: '人力', value: 25 }
]
// è¾¾æ ‡çŽ‡è¶‹åŠ¿å›¾é…ç½®
const complianceTrendOptions = computed(() => ({
    tooltip: {
        trigger: 'axis',
        formatter: '{b}<br/>达标率: {c}%'
    },
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
    },
    xAxis: {
        type: 'category',
        data: mockComplianceData.map(item => item.date),
        axisLabel: {
            rotate: 45
        }
    },
    yAxis: {
        type: 'value',
        name: '达标率 (%)',
        min: 70,
        max: 100
    },
    series: [
        {
            name: '达标率',
            type: 'line',
            smooth: true,
            data: mockComplianceData.map(item => item.rate),
            itemStyle: {
                color: '#409EFF'
            },
            lineStyle: {
                width: 3
            },
            areaStyle: {
                color: {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: [{
                        offset: 0,
                        color: 'rgba(64, 158, 255, 0.3)'
                    }, {
                        offset: 1,
                        color: 'rgba(64, 158, 255, 0.05)'
                    }]
                }
            }
        }
    ]
}))
// ç…¤ç§å‘热量对比图配置
const calorificValueOptions = computed(() => ({
    tooltip: {
        trigger: 'axis',
        axisPointer: {
            type: 'shadow'
        },
        formatter: '{b}<br/>发热量: {c} å¤§å¡'
    },
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
    },
    xAxis: {
        type: 'category',
        data: mockCalorificData.map(item => item.name)
    },
    yAxis: {
        type: 'value',
        name: '发热量 (大卡)'
    },
    series: [
        {
            name: '发热量',
            type: 'bar',
            data: mockCalorificData.map(item => item.value),
            itemStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    { offset: 0, color: '#FF6B81' },
                    { offset: 1, color: '#FF8E53' }
                ])
            },
            label: {
                show: true,
                position: 'top',
                formatter: '{c}大卡'
            }
        }
    ]
}))
// é…æ–¹ä½¿ç”¨é¢‘次图配置
const recipeFrequencyOptions = computed(() => ({
    tooltip: {
        trigger: 'item',
        formatter: '{b}: {c}次 ({d}%)'
    },
    legend: {
        orient: 'vertical',
        left: 'left',
        textStyle: {
            fontSize: 12
        }
    },
    series: [
        {
            name: '配方使用频次',
            type: 'pie',
            radius: '60%',
            center: ['60%', '50%'],
            data: mockRecipeData,
            emphasis: {
                itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
            },
            itemStyle: {
                borderRadius: 8,
                borderColor: '#fff',
                borderWidth: 2
            },
            label: {
                show: true,
                formatter: '{b}\n{c}次'
            }
        }
    ]
}))
// åŠ å·¥å¾—çŽ‡åˆ†æžå›¾é…ç½®
const processingYieldOptions = computed(() => ({
    tooltip: {
        trigger: 'axis',
        axisPointer: {
            type: 'shadow'
        }
    },
    legend: {
        data: ['出洗率', '加工能效']
    },
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
    },
    xAxis: {
        type: 'category',
        data: mockYieldData.map(item => item.name)
    },
    yAxis: {
        type: 'value',
        name: '百分比 (%)',
        min: 60,
        max: 100
    },
    series: [
        {
            name: '出洗率',
            type: 'bar',
            data: mockYieldData.map(item => item.yield),
            itemStyle: {
                color: '#52C41A'
            }
        },
        {
            name: '加工能效',
            type: 'bar',
            data: mockYieldData.map(item => item.efficiency),
            itemStyle: {
                color: '#1890FF'
            }
        }
    ]
}))
// æˆæœ¬ç»“构图谱配置
const costStructureOptions = computed(() => ({
    tooltip: {
        trigger: 'item',
        formatter: '{b}: {c}%'
    },
    legend: {
        orient: 'vertical',
        left: 'left'
    },
    series: [
        {
            name: '成本结构',
            type: 'pie',
            radius: '60%',
            center: ['60%', '50%'],
            data: mockCostData,
            emphasis: {
                itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
            },
            roseType: 'radius',
            itemStyle: {
                borderRadius: 8,
                borderColor: '#fff',
                borderWidth: 2
            }
        }
    ]
}))
// æŸ¥è¯¢æ•°æ®
const loading = ref(false)
const handleSearch = async () => {
    try {
        loading.value = true
        // åœ¨å®žé™…应用中,这里会调用API获取真实数据
        console.log('搜索参数:', searchParams.value)
        // èŽ·å–é…ç…¤æ•°æ®
        // const blendingRes = await getCoalBlendingList({
        //   startTime: searchParams.value.dateRange[0] ? parseTime(searchParams.value.dateRange[0]) : '',
        //   endTime: searchParams.value.dateRange[1] ? parseTime(searchParams.value.dateRange[1]) : ''
        // })
        // æ¨¡æ‹ŸAPI请求延迟
        await new Promise(resolve => setTimeout(resolve, 800))
        // æ¨¡æ‹Ÿæ•°æ®æ›´æ–°ï¼ˆå®žé™…应用中应根据API返回结果更新)
        unitHeatCoalConsumption.value = 31.8 + Math.random() * 5
        overallComplianceRate.value = 80 + Math.random() * 15
        averageCalorificValue.value = 5000 + Math.random() * 500
        totalBatches.value = 40 + Math.floor(Math.random() * 20)
        // éšæœºæ›´æ–°éƒ¨åˆ†å›¾è¡¨æ•°æ®
        mockComplianceData.forEach(item => {
            item.rate = 80 + Math.random() * 15
        })
        ElMessage.success('查询成功')
    } catch (error) {
        console.error('查询失败:', error)
        ElMessage.error('查询失败,请稍后重试')
    } finally {
        loading.value = false
    }
}
// åˆ·æ–°æ•°æ®
const handleRefresh = () => {
    ElMessage.info('正在刷新数据...')
    handleSearch()
}
// å¯¼å‡ºæ•°æ®
const handleExport = async () => {
    try {
        loading.value = true
        // æ¨¡æ‹Ÿå¯¼å‡ºæ“ä½œ
        await new Promise(resolve => setTimeout(resolve, 1000))
        ElMessage.success('数据导出成功')
    } catch (error) {
        console.error('导出失败:', error)
        ElMessage.error('导出失败,请稍后重试')
    } finally {
        loading.value = false
    }
}
// é‡ç½®æœç´¢å‚æ•°
const handleReset = () => {
    searchParams.value = {
        dateRange: []
    }
}
// åˆå§‹åŒ–页面
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>
.coal-blending-efficiency {
    padding: 20px;
}
.page-header {
    margin-bottom: 20px;
}
.page-header h2 {
    margin-bottom: 15px;
    font-size: 18px;
    font-weight: 600;
    color: #303133;
}
.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: #ECF5FF;
    color: #409EFF;
}
.stat-card:nth-child(2) .card-icon {
    background: #F0F9FF;
    color: #69B1FF;
}
.stat-card:nth-child(3) .card-icon {
    background: #F6FFED;
    color: #52C41A;
}
.stat-card:nth-child(4) .card-icon {
    background: #FFF7E6;
    color: #FAAD14;
}
.card-content {
    flex: 1;
}
.card-title {
    font-size: 14px;
    color: #606266;
    margin-bottom: 8px;
}
.card-value {
    font-size: 24px;
    font-weight: 600;
    color: #303133;
    margin-bottom: 4px;
}
.card-desc {
    font-size: 12px;
    color: #909399;
}
.charts-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
    gap: 24px;
}
.chart-card {
    background: #fff;
    border-radius: 8px;
    overflow: hidden;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px;
    border-bottom: 1px solid #EBEEF5;
}
.card-header span {
    font-weight: 600;
    color: #303133;
}
.chart-wrapper {
    padding: 20px;
}
.cost-structure {
    grid-column: 1 / -1;
}
@media (max-width: 1200px) {
    .charts-container {
        grid-template-columns: 1fr;
    }
}
</style>
src/views/productionControl/stockMaterialBoard/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,636 @@
<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>
src/views/salesOutbound/components/formDia.vue
@@ -37,9 +37,9 @@
              <el-select v-model="form.coalId" placeholder="请选择煤种" @change="setInfo" :disabled="operationType === 'view'">
                <el-option
                    v-for="item in coalOptions"
                    :key="item.coalId"
                    :key="item.id"
                    :label="item.coal"
                    :value="item.coalId"
                    :value="item.id"
                />
              </el-select>
            </el-form-item>