Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁_泽淇实业

# Conflicts:
# src/views/productionManagement/productionReporting/index.vue
已添加4个文件
已修改21个文件
595 ■■■■■ 文件已修改
src/views/equipmentManagement/upkeep/Form/RecordDetailDia.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/detailDia.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/psiNavigation.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/PanelHeader.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-center.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/center-top.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/left-top.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/components/right-top.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionAnalysis/productionNavigation.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/indicatorStats/index.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/RecordDetailDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        title="保养记录详情"
        width="800px"
        operation-type="detail"
        @close="cancel"
    >
        <el-descriptions border :column="2">
            <el-descriptions-item label="设备名称">
                {{ detailData.deviceName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="规格型号">
                {{ detailData.deviceModel || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="计划保养日期">
                {{ formatDate(detailData.maintenancePlanTime) }}
            </el-descriptions-item>
            <el-descriptions-item label="录入人">
                {{ detailData.createUserName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养项目" :span="2">
                {{ detailData.machineryCategory || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="实际保养人">
                {{ detailData.maintenanceActuallyName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="实际保养日期">
                {{ formatDate(detailData.maintenanceActuallyTime) }}
            </el-descriptions-item>
            <el-descriptions-item label="保养结果" :span="2">
                {{ detailData.maintenanceResult || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="状态" :span="2">
                <el-tag v-if="detailData.status === 2" type="danger">失败</el-tag>
                <el-tag v-else-if="detailData.status === 1" type="success">完结</el-tag>
                <el-tag v-else type="warning">待保养</el-tag>
            </el-descriptions-item>
        </el-descriptions>
        <div v-if="fileList && fileList.length > 0" class="image-section">
            <div class="image-title">保养附件(图片):</div>
            <AttachmentUploadImage
                v-model:fileList="fileList"
                :disabled="true"
            />
        </div>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
import dayjs from "dayjs";
import { attachmentList } from "@/api/basicData/storageAttachment.js";
import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue';
const dialogVisitable = ref(false);
const detailData = ref({});
const fileList = ref([]);
const formatDate = (date) => {
    return date ? dayjs(date).format("YYYY-MM-DD") : "--";
};
const openDialog = (row) => {
    dialogVisitable.value = true;
    detailData.value = { ...row };
    fileList.value = [];
    if (row.id) {
        attachmentList({
            recordType: 'device_maintenance',
            recordId: row.id,
        }).then(res => {
            if (res && res.data) {
                fileList.value = res.data || [];
            }
        });
    }
};
const cancel = () => {
    dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped>
.image-section {
    margin-top: 20px;
}
.image-title {
    font-weight: bold;
    margin-bottom: 10px;
    color: #606266;
}
</style>
src/views/equipmentManagement/upkeep/Form/detailDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        title="保养任务详情"
        width="800px"
        operation-type="detail"
        @close="cancel"
    >
        <el-descriptions border :column="2">
            <el-descriptions-item label="设备名称">
                {{ detailData.taskName || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="规格型号">
                {{ detailData.deviceModel || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="录入人">
                {{ detailData.registrant || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="登记时间">
                {{ detailData.registrationDate || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养项目" :span="2">
                {{ detailData.machineryCategory || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="保养人">
                {{ detailData.maintenancePerson || '--' }}
            </el-descriptions-item>
            <el-descriptions-item label="任务频率">
                {{ formatFrequencyType(detailData.frequencyType) }}
            </el-descriptions-item>
            <el-descriptions-item label="执行时间">
                {{ formatFrequencyDetail(detailData.frequencyDetail) }}
            </el-descriptions-item>
            <el-descriptions-item label="定时任务">
                <el-tag :type="detailData.isActive === 1 ? 'success' : 'info'">
                    {{ detailData.isActive === 1 ? '开启' : '关闭' }}
                </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="备注" :span="2">
                {{ detailData.remarks || '--' }}
            </el-descriptions-item>
        </el-descriptions>
        <div v-if="fileList && fileList.length > 0" class="image-section">
            <div class="image-title">设备图片:</div>
            <AttachmentUploadImage
                v-model:fileList="fileList"
                :disabled="true"
            />
        </div>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { ref } from "vue";
import { getLedgerById } from "@/api/equipmentManagement/ledger";
import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue';
const dialogVisitable = ref(false);
const detailData = ref({});
const fileList = ref([]);
const formatFrequencyType = (type) => {
    const map = {
        DAILY: "每日",
        WEEKLY: "每周",
        MONTHLY: "每月",
        QUARTERLY: "季度",
    };
    return map[type] || "--";
};
const formatFrequencyDetail = (detail) => {
    if (!detail) return "--";
    const replacements = {
        MON: "周一",
        TUE: "周二",
        WED: "周三",
        THU: "周四",
        FRI: "周五",
        SAT: "周六",
        SUN: "周日",
    };
    return detail.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
};
const openDialog = (row) => {
    dialogVisitable.value = true;
    detailData.value = { ...row };
    fileList.value = [];
    if (row.taskId) {
        getLedgerById(row.taskId).then(res => {
            if (res.code === 200 && res.data) {
                fileList.value = res.data.storageBlobVOs || [];
            }
        });
    }
};
const cancel = () => {
    dialogVisitable.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped>
.image-section {
    margin-top: 20px;
}
.image-title {
    font-weight: bold;
    margin-bottom: 10px;
    color: #606266;
}
</style>
src/views/equipmentManagement/upkeep/index.vue
@@ -78,6 +78,11 @@
            <template #operation="{ row }">
              <el-button type="primary"
                         link
                         @click="handleDetail(row)">
                è¯¦æƒ…
              </el-button>
              <el-button type="primary"
                         link
                         @click="editScheduledTask(row)">
                ç¼–辑
              </el-button>
@@ -177,6 +182,11 @@
                      type="warning">待保养</el-tag>
            </template>
            <template #operation="{ row }">
              <el-button type="primary"
                         link
                         @click="handleRecordDetail(row)">
                è¯¦æƒ…
              </el-button>
              <!-- è¿™ä¸ªåŠŸèƒ½è·Ÿæ–°å¢žä¿å…»åŠŸèƒ½ä¸€æ¨¡ä¸€æ ·ï¼Œæœ‰å•¥æ„ä¹‰ï¼Ÿ -->
              <!-- <el-button
              type="primary"
@@ -219,6 +229,8 @@
                      @ok="getTableData" />
    <FormDia ref="formDiaRef"
             @closeDia="getScheduledTableData" />
    <DetailDia ref="detailDiaRef" />
    <RecordDetailDia ref="recordDetailDiaRef" />
    <FileList v-if="fileDialogVisible"
              v-model:visible="fileDialogVisible"
              :record-type="'device_maintenance'"
@@ -241,6 +253,8 @@
  import PlanModal from "./Form/PlanModal.vue";
  import MaintenanceModal from "./Form/MaintenanceModal.vue";
  import FormDia from "./Form/formDia.vue";
  import DetailDia from "./Form/detailDia.vue";
  import RecordDetailDia from "./Form/RecordDetailDia.vue";
  import {
    getUpkeepPage,
    delUpkeep,
@@ -263,6 +277,10 @@
  const maintainModalRef = ref();
  // å®šæ—¶ä»»åŠ¡å¼¹çª—æŽ§åˆ¶å™¨
  const formDiaRef = ref();
  // å®šæ—¶ä»»åŠ¡è¯¦æƒ…å¼¹çª—æŽ§åˆ¶å™¨
  const detailDiaRef = ref();
  // ä¿å…»è®°å½•详情弹窗控制器
  const recordDetailDiaRef = ref();
  // é™„件弹窗
  const fileListDialogRef = ref(null);
  const fileDialogVisible = ref(false);
@@ -314,7 +332,7 @@
    {
      prop: "frequencyType",
      label: "频次",
      minWidth: 50,
      minWidth: 80,
      // PIMTable ä½¿ç”¨çš„æ˜¯ formatData,而不是 Element-Plus çš„ formatter
      formatData: cell =>
        ({
@@ -371,7 +389,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "150px",
      width: "160px",
    },
  ]);
@@ -445,7 +463,7 @@
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "350px",
      width: "250px",
    },
  ]);
@@ -503,6 +521,14 @@
    if (row) {
      nextTick(() => {
        formDiaRef.value?.openDialog("edit", row);
      });
    }
  };
  const handleDetail = row => {
    if (row) {
      nextTick(() => {
        detailDiaRef.value?.openDialog(row);
      });
    }
  };
@@ -578,6 +604,14 @@
    maintainModalRef.value.open(row.id, row);
  };
  const handleRecordDetail = row => {
    if (row) {
      nextTick(() => {
        recordDetailDiaRef.value?.openDialog(row);
      });
    }
  };
  const addPlan = () => {
    planModalRef.value.openModal();
  };
src/views/productionManagement/productionReporting/index.vue
@@ -180,27 +180,27 @@
    {
      label: "报工单号",
      prop: "productNo",
      width: 120,
      width: 140,
    },
    {
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
      label: "工时(h)",
      width: 100,
      prop: "workHour",
    },
    // {
    //   label: "工时(h)",
    //   width: 100,
    //   prop: "workHour",
    // },
    {
      label: "工序",
      prop: "process",
      width: 120,
      width: 100,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
      width: 140,
    },
    {
      label: "销售合同号",
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue
@@ -1,17 +1,35 @@
<template>
  <div class="panel-header">
  <div
    class="panel-header"
    :class="{ clickable: !!to }"
    @click="handleClick"
  >
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
<script setup>
defineProps({
import { useRouter } from 'vue-router'
const props = defineProps({
  title: {
    type: String,
    required: true,
    default: ''
  },
  to: {
    type: String,
    default: ''
  }
})
const router = useRouter()
const handleClick = () => {
  if (props.to) {
    router.push(props.to)
  }
}
</script>
<style scoped>
@@ -30,4 +48,12 @@
  padding-left: 46px;
  line-height: 36px;
}
.panel-header.clickable {
  cursor: pointer;
}
.panel-header.clickable:hover .panel-title {
  color: #43e8fc;
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="出入库趋势" />
    <PanelHeader title="出入库趋势" to="/inventoryManagement/receiptManagement" />
    <div class="main-panel panel-item-customers">
      <div class="filters-row">
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -2,7 +2,7 @@
  <div>
    <!-- è®¾å¤‡ç»Ÿè®¡ -->
    <div class="equipment-stats">
      <div class="equipment-header">
      <div class="equipment-header clickable" @click="handleNavigate">
        <img
          src="@/assets/BI/shujutongjiicon@2x.png"
          alt="图标"
@@ -28,8 +28,19 @@
<script setup>
import { ref, onMounted, inject, watch } from 'vue'
import { useRouter } from 'vue-router'
import Echarts from '@/components/Echarts/echarts.vue'
import { productTurnoverDays } from '@/api/viewIndex.js'
import { getPsiRoute } from '../psiNavigation.js'
const router = useRouter()
const handleNavigate = () => {
  const path = getPsiRoute('产品周转天数')
  if (path) {
    router.push(path)
  }
}
const chartStyle = { width: '100%', height: '100%' }
const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true }
@@ -119,6 +130,14 @@
  padding-bottom: 2px;
}
.equipment-header.clickable {
  cursor: pointer;
}
.equipment-header.clickable:hover .equipment-title {
  opacity: 0.85;
}
.equipment-title {
  font-weight: 500;
  font-size: 18px;
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -6,6 +6,8 @@
        v-for="item in statItems"
        :key="item.name"
        class="stat-card"
        :class="{ clickable: !!getStatRoute(item.name) }"
        @click="handleStatClick(item.name)"
      >
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
@@ -25,9 +27,21 @@
<script setup>
import { ref, onMounted, inject, watch } from 'vue'
import { useRouter } from 'vue-router'
import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js'
import { getPsiRoute } from '../psiNavigation.js'
const router = useRouter()
const statItems = ref([])
const getStatRoute = (name) => getPsiRoute(name)
const handleStatClick = (name) => {
  const path = getStatRoute(name)
  if (path) {
    router.push(path)
  }
}
const formatPercent = (val) => {
  const num = Number(val) || 0
@@ -81,6 +95,14 @@
  height: 142px;
}
.stat-card.clickable {
  cursor: pointer;
}
.stat-card.clickable:hover .card-label {
  color: #43e8fc;
}
.card-icon {
  width: 100px;
  height: 100px;
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="采购品分布" />
    <PanelHeader title="采购品分布" to="/procurementManagement/procurementLedger" />
    <div class="main-panel panel-item-customers">
      <CarouselCards :items="cardItems" :visible-count="3" />
      <div class="pie-chart-wrapper" ref="pieWrapperRef">
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="销售品分布" />
    <PanelHeader title="销售品分布" to="/salesManagement/salesLedger" />
    <div class="main-panel panel-item-customers">
      <CarouselCards :items="cardItems" :visible-count="3" />
      <div class="pie-chart-wrapper" ref="pieWrapperRef">
src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -34,8 +34,8 @@
      <!-- å³ä¾§åŒºåŸŸ -->
      <div class="right-panel">
        <RightBottom />
        <RightTop />
        <RightBottom header-to="/salesManagement/receiptPaymentLedger" />
        <RightTop header-to="/procurementManagement/paymentLedger" />
      </div>
    </div>
    </div>
src/views/reportAnalysis/PSIDataAnalysis/psiNavigation.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
export const PSI_ROUTE_MAP = {
  é”€å”®å“åˆ†å¸ƒ: '/salesManagement/salesLedger',
  é‡‡è´­å“åˆ†å¸ƒ: '/procurementManagement/procurementLedger',
  é”€å”®äº§å“æ•°: '/salesManagement/salesLedger',
  é‡‡è´­äº§å“æ•°: '/procurementManagement/procurementLedger',
  å‚¨å­˜äº§å“æ•°: '/inventoryManagement/stockManagement',
  å®¢æˆ·è´¡çŒ®æŽ’名: '/salesManagement/receiptPaymentLedger',
  ä¾›åº”商采购排名: '/procurementManagement/paymentLedger',
  å‡ºå…¥åº“趋势: '/inventoryManagement/receiptManagement',
  äº§å“å‘¨è½¬å¤©æ•°: '/inventoryManagement/stockManagement',
}
export function getPsiRoute(name) {
  return PSI_ROUTE_MAP[name] || ''
}
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue
@@ -1,17 +1,35 @@
<template>
  <div class="panel-header">
  <div
    class="panel-header"
    :class="{ clickable: !!to }"
    @click="handleClick"
  >
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
<script setup>
defineProps({
import { useRouter } from 'vue-router'
const props = defineProps({
  title: {
    type: String,
    required: true,
    default: ''
  },
  to: {
    type: String,
    default: ''
  }
})
const router = useRouter()
const handleClick = () => {
  if (props.to) {
    router.push(props.to)
  }
}
</script>
<style scoped>
@@ -30,4 +48,12 @@
  padding-left: 46px;
  line-height: 36px;
}
.panel-header.clickable {
  cursor: pointer;
}
.panel-header.clickable:hover .panel-title {
  color: #43e8fc;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="客户贡献排名" />
    <PanelHeader title="客户贡献排名" :to="headerTo" />
    <div class="panel-item-customers">
      <div class="switch-container">
        <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
@@ -28,6 +28,13 @@
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { customerContributionRanking } from '@/api/viewIndex.js'
defineProps({
  headerTo: {
    type: String,
    default: ''
  }
})
const chartStyle = {
  width: '100%',
  height: '100%',
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="供应商采购排名" />
    <PanelHeader title="供应商采购排名" :to="headerTo" />
    <div class="panel-item-customers">
      <div class="switch-container">
        <DateTypeSwitch v-model="radio1" @change="handleDateTypeChange" />
@@ -27,6 +27,13 @@
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { supplierPurchaseRanking } from '@/api/viewIndex.js'
defineProps({
  headerTo: {
    type: String,
    default: ''
  }
})
const chartStyle = {
  width: '100%',
  height: '100%',
src/views/reportAnalysis/productionAnalysis/components/PanelHeader.vue
@@ -1,17 +1,35 @@
<template>
  <div class="panel-header">
  <div
    class="panel-header"
    :class="{ clickable: !!to }"
    @click="handleClick"
  >
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
<script setup>
defineProps({
import { useRouter } from 'vue-router'
const props = defineProps({
  title: {
    type: String,
    required: true,
    default: ''
  },
  to: {
    type: String,
    default: ''
  }
})
const router = useRouter()
const handleClick = () => {
  if (props.to) {
    router.push(props.to)
  }
}
</script>
<style scoped>
@@ -30,4 +48,12 @@
  padding-left: 46px;
  line-height: 36px;
}
.panel-header.clickable {
  cursor: pointer;
}
.panel-header.clickable:hover .panel-title {
  color: #43e8fc;
}
</style>
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="生产订单完成进度" />
    <PanelHeader title="生产订单完成进度" to="/productionManagement/productionOrder" />
    <div class="main-panel">
      <div class="panel-item-customers">
        <CarouselCards :items="cardItems" :visible-count="4" />
src/views/reportAnalysis/productionAnalysis/components/center-center.vue
@@ -2,7 +2,7 @@
  <div>
    <!-- è®¾å¤‡ç»Ÿè®¡ -->
    <div class="equipment-stats">
      <div class="equipment-header">
      <div class="equipment-header clickable" @click="handleNavigate">
        <img
          src="@/assets/BI/shujutongjiicon@2x.png"
          alt="图标"
@@ -32,10 +32,21 @@
<script setup>
import { ref, onMounted, inject, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
import { inputOutputAnalysis } from '@/api/viewIndex.js'
import DateTypeSwitch from "@/views/reportAnalysis/productionAnalysis/components/DateTypeSwitch.vue";
import { getProductionRoute } from '../productionNavigation.js'
const router = useRouter()
const handleNavigate = () => {
  const path = getProductionRoute('投入产出分析')
  if (path) {
    router.push(path)
  }
}
const dateType = ref(3) // 1=周 2=月 3=季度
const chartRef = ref(null)
@@ -204,6 +215,14 @@
  padding-bottom: 2px;
}
.equipment-header.clickable {
  cursor: pointer;
}
.equipment-header.clickable:hover .equipment-title {
  opacity: 0.85;
}
.equipment-title {
  font-weight: 500;
  font-size: 18px;
src/views/reportAnalysis/productionAnalysis/components/center-top.vue
@@ -6,6 +6,8 @@
        v-for="item in statItems"
        :key="item.name"
        class="stat-card"
        :class="{ clickable: !!getStatRoute(item.name) }"
        @click="handleStatClick(item.name)"
      >
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
@@ -25,9 +27,21 @@
<script setup>
import { ref, onMounted, inject, watch } from 'vue'
import { useRouter } from 'vue-router'
import { orderCount } from '@/api/viewIndex.js'
import { getProductionRoute } from '../productionNavigation.js'
const router = useRouter()
const statItems = ref([])
const getStatRoute = (name) => getProductionRoute(name)
const handleStatClick = (name) => {
  const path = getStatRoute(name)
  if (path) {
    router.push(path)
  }
}
const formatPercent = (val) => {
  const num = Number(val) || 0
@@ -81,6 +95,14 @@
  height: 142px;
}
.stat-card.clickable {
  cursor: pointer;
}
.stat-card.clickable:hover .card-label {
  color: #43e8fc;
}
.card-icon {
  width: 100px;
  height: 100px;
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="在制品统计分析" />
    <PanelHeader title="在制品统计分析" to="/productionManagement/productionOrder" />
    <div class="main-panel panel-item-customers">
      <CarouselCards :items="cardItems" :visible-count="3" />
      <div class="chart-wrapper">
src/views/reportAnalysis/productionAnalysis/components/left-top.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="工序产出分析" />
    <PanelHeader title="工序产出分析" to="/productionManagement/processStatistics" />
    <div class="main-panel panel-item-customers">
      <div class="filters-row">
        <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="生产核算分析" />
    <PanelHeader title="生产核算分析" to="/productionManagement/productionCosting" />
    <div class="main-panel panel-item-customers">
      <div class="filters-row">
        <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
src/views/reportAnalysis/productionAnalysis/components/right-top.vue
@@ -1,6 +1,6 @@
<template>
  <div>
    <PanelHeader title="工单执行效率分析" />
    <PanelHeader title="工单执行效率分析" to="/productionManagement/productionManagement/workOrderEdit/index" />
    <div class="main-panel panel-item-customers">
      <div class="filters-row">
        <DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
src/views/reportAnalysis/productionAnalysis/productionNavigation.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
const PRODUCTION_ORDER_ROUTE = '/productionManagement/productionOrder'
export const PRODUCTION_ROUTE_MAP = {
  å·¥åºäº§å‡ºåˆ†æž: '/productionManagement/processStatistics',
  åœ¨åˆ¶å“ç»Ÿè®¡åˆ†æž: PRODUCTION_ORDER_ROUTE,
  ç”Ÿäº§è®¢å•æ•°: PRODUCTION_ORDER_ROUTE,
  å·²å®Œæˆè®¢å•æ•°: PRODUCTION_ORDER_ROUTE,
  å¾…生产订单: PRODUCTION_ORDER_ROUTE,
  å¾…生产订单数: PRODUCTION_ORDER_ROUTE,
  ç”Ÿäº§è®¢å•完成进度: PRODUCTION_ORDER_ROUTE,
  ç”Ÿäº§è®¢å•完成进度数: PRODUCTION_ORDER_ROUTE,
  æŠ•入产出分析: '/productionManagement/productionReporting',
  å·¥å•执行效率分析: '/productionManagement/productionManagement/workOrderEdit/index',
  ç”Ÿäº§æ ¸ç®—分析: '/productionManagement/productionCosting',
}
export function getProductionRoute(name) {
  return PRODUCTION_ROUTE_MAP[name] || ''
}
src/views/salesManagement/indicatorStats/index.vue
@@ -2,7 +2,7 @@
  <div class="app-container indicator-stats">
    <!-- KPI æ±‡æ€» -->
    <el-row :gutter="20" class="stats-row">
      <el-col :xs="24" :sm="12" :md="8">
      <el-col :xs="24" :sm="12" :md="6">
        <div class="stat-card stat-card-blue">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
@@ -16,7 +16,7 @@
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :md="8">
      <el-col :xs="24" :sm="12" :md="6">
        <div class="stat-card stat-card-green">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
@@ -30,7 +30,21 @@
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :md="8">
      <el-col :xs="24" :sm="12" :md="6">
        <div class="stat-card stat-card-purple">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
              <el-icon :size="32"><Goods /></el-icon>
            </div>
          </div>
          <div class="stat-content">
            <div class="stat-value">{{ formatQuantity(indicatorKpis.productSalesQuantity) }}</div>
            <div class="stat-label">产品销售数量</div>
          </div>
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :md="6">
        <div class="stat-card stat-card-orange">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
@@ -132,7 +146,7 @@
<script setup>
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { Document, Van, Tickets, Search, Refresh } from '@element-plus/icons-vue'
import { Document, Van, Tickets, Search, Refresh, Goods } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats'
import { productTreeList } from '@/api/basicData/product.js'
@@ -142,6 +156,7 @@
const indicatorKpis = reactive({
  orderCount: 0,
  salesAmount: 0,
  productSalesQuantity: 0,
  shipRate: 0
})
@@ -161,6 +176,11 @@
const productOptions = ref([])
const customerOption = ref([])
const formatQuantity = (value) => {
  const num = Number(value)
  if (Number.isNaN(num)) return '0'
  return num.toLocaleString(undefined, { maximumFractionDigits: 2 })
}
// è½¬æ¢äº§å“æ ‘数据,将 id æ”¹ä¸º value
function convertIdToValue(data) {
  return data.map((item) => {
@@ -234,7 +254,7 @@
    if (res && res.data) {
      indicatorKpis.orderCount = res.data.total || 0
      indicatorKpis.salesAmount = res.data.contractAmountTotal || 0
      // å‘货率如果接口没有返回,保持原值或设为0
      indicatorKpis.productSalesQuantity = res.data.productQuantityTotal || 0
      indicatorKpis.shipRate = res.data.shipRate || 0
    }
  } catch (error) {
@@ -282,12 +302,19 @@
  if (indicatorChart) indicatorChart.dispose()
  indicatorChart = echarts.init(indicatorChartRef.value)
  
  // æ ¹æ®æŽ¥å£è¿”回的数据结构更新图表
  // æŽ¥å£è¿”回: dateList, orderCountList, salesAmountList
  // æŽ¥å£è¿”回: dateList, orderCountList, salesAmountList, productQuantityList
  const option = {
    title: { text: '多维度销售指标趋势', left: 'center' },
    tooltip: { trigger: 'axis' },
    legend: { data: ['订单数', '销售额'], top: 30 },
    legend: {
      data: ['订单数', '销售额', '产品销售数量'],
      top: 30,
      selected: {
        '订单数': false,
        '销售额': true,
        '产品销售数量': true
      }
    },
    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
    xAxis: { 
      type: 'category', 
@@ -322,6 +349,13 @@
        yAxisIndex: 0,
        data: chartData.salesAmountList || [], 
        itemStyle: { color: '#67c23a' } 
      },
      {
        name: '产品销售数量',
        type: 'line',
        yAxisIndex: 1,
        data: chartData.productQuantityList || [],
        itemStyle: { color: '#f56c6c' }
      }
    ]
  }
@@ -335,7 +369,15 @@
  const option = {
    title: { text: '多维度销售指标趋势', left: 'center' },
    tooltip: { trigger: 'axis' },
    legend: { data: ['订单数', '销售额'], top: 30 },
    legend: {
      data: ['订单数', '销售额', '产品销售数量'],
      top: 30,
      selected: {
        '订单数': false,
        '销售额': true,
        '产品销售数量': true
      }
    },
    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
    xAxis: { type: 'category', data: [] },
    yAxis: [
@@ -355,7 +397,8 @@
    ],
    series: [
      { name: '订单数', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#409eff' } },
      { name: '销售额', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }
      { name: '销售额', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } },
      { name: '产品销售数量', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#f56c6c' } }
    ]
  }
  indicatorChart.setOption(option)
@@ -523,6 +566,17 @@
      background: #e6a23c;
    }
  }
  &.stat-card-purple {
    .stat-icon {
      background: linear-gradient(135deg, #9b59b6 0%, #b37fcc 100%);
      color: #fff;
    }
    .stat-bg-decoration {
      background: #9b59b6;
    }
  }
}
.chart-card,