gongchunyi
3 天以前 f281afc3ae596f649340bfc592b746917e28d701
Merge branch 'dev_银川_中盛建材' of http://114.132.189.42:9002/r/product-inventory-management into dev_银川_中盛建材
已添加5个文件
已修改4个文件
3950 ■■■■ 文件已修改
src/api/basicData/newProduct.js 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/costAccounting/energyCosts.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/energyManagement/energyType.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ImportExcel/index.vue 197 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 1323 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/xxx.js 615 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/costAccounting/energyCosts/index.vue 1135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/energyConsumptionStatistical/index.vue 117 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/unitEnergyConsumption/index.vue 405 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/newProduct.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
// äº§å“ç»´æŠ¤é¡µé¢æŽ¥å£
import request from '@/utils/request'
// äº§å“æ ‘查询
export function productTreeList(query) {
    return request({
        url: '/productMaterial/list',
        method: 'get',
        params: query
    })
}
// äº§å“å­ç±»æ–°å¢ž
export function addOrEditProduct(query) {
    return request({
        url: '/productMaterial/add',
        method: 'post',
        data: query
    })
}
// äº§å“å­ç±»ä¿®æ”¹
export function updateOrEditProduct(query) {
    return request({
        url: '/productMaterial/update',
        method: 'put',
        data: query
    })
}
// è§„格型号新增
export function addOrEditProductModel(query) {
    return request({
        url: '/productMaterialSku/add',
        method: 'post',
        data: query
    })
}
// è§„格型号修改
export function updateOrEditProductModel(query) {
    return request({
        url: '/productMaterialSku/update',
        method: 'put',
        data: query
    })
}
// äº§å“å­ç±»åˆ é™¤
export function delProduct(query) {
    return request({
        url: '/productMaterial/delete',
        method: 'delete',
        data: query
    })
}
// è§„格型号删除
export function delProductModel(query) {
    return request({
        url: '/productMaterialSku/delete',
        method: 'delete',
        data: query
    })
}
// è§„格型号查询
export function modelList(query) {
    return request({
        url: '/basic/product/modelList',
        method: 'get',
        params: query
    })
}
export function modelListPage(query) {
    return request({
        url: '/productMaterialSku/list',
        method: 'get',
        params: query
    })
}
//  ä¸‹è½½äº§å“å¯¼å…¥æ¨¡æ¿
export function downloadProductModelImportTemplate() {
    return request({
        url: '/productMaterialSku/downloadTemplate',
        method: 'post',
        responseType: 'blob'
    })
}
// äº§å“å¤§ç±»æ–°å¢ž
export function addOrEditProductConfig(query) {
    return request({
        url: '/productMaterial/config/add',
        method: 'post',
        data: query
    })
}
// äº§å“å¤§ç±»ä¿®æ”¹
export function updateOrEditProductConfig(query) {
    return request({
        url: '/productMaterial/config/update',
        method: 'put',
        data: query
    })
}
// äº§å“å¤§ç±»åˆ é™¤
export function delProductConfig(query) {
    return request({
        url: '/productMaterial/config/delete',
        method: 'delete',
        data: query
    })
}
// äº§å“ç‰©æ–™ä¿¡æ¯-存货类别数据集合
export function getinventoryCategoryList(query) {
    return request({
        url: '/productMaterial/inventoryCategoryList',
        method: 'get',
        params: query
    })
}
// äº§å“ç‰©æ–™ä¿¡æ¯-物料类型数据集合
export function getmaterialTypeList(query) {
    return request({
        url: '/productMaterial/materialTypeList',
        method: 'get',
        params: query
    })
}
src/api/costAccounting/energyCosts.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
// èƒ½è€—成本核算接口
import request from "@/utils/request";
// èƒ½è€—成本统计
export function energyCostStatistics(query) {
  return request({
    url: "/energyCost/statistics",
    method: "get",
    params: query,
  });
}
// å¯¼å‡ºèƒ½è€—成本报表
export function exportEnergyCostReport(query) {
  return request({
    url: "/energyCost/export",
    method: "get",
    params: query,
    responseType: "blob",
  });
}
src/api/energyManagement/energyType.js
@@ -75,4 +75,13 @@
    url: `/energyConsumptionDetailFile/${ids}`,
    method: 'delete',
  })
}
}
// èƒ½è€—抄表明细-统计查询
export function energyConsumptionDetailStatistics(query) {
  return request({
    url: "/energyConsumptionDetail/statistics",
    method: "get",
    params: query,
  });
}
src/views/basicData/product/ImportExcel/index.vue
@@ -1,14 +1,25 @@
<template>
  <el-button type="info" plain icon="Upload" @click="handleImport">
  <el-button type="info"
             plain
             icon="Upload"
             @click="handleImport">
    å¯¼å…¥
  </el-button>
  <el-dialog v-model="upload.open" :title="upload.title" @close="handleDialogClose">
    <FileUpload ref="fileUploadRef" accept=".xlsx, .xls" :headers="upload.headers" :action="uploadUrl"
      :disabled="upload.isUploading" :showTip="true" @success="handleFileSuccess"
      :downloadTemplate="handleDownloadTemplate" />
  <el-dialog v-model="upload.open"
             :title="upload.title"
             @close="handleDialogClose">
    <FileUpload ref="fileUploadRef"
                accept=".xlsx, .xls"
                :headers="upload.headers"
                :action="uploadUrl"
                :disabled="upload.isUploading"
                :showTip="true"
                @success="handleFileSuccess"
                :downloadTemplate="handleDownloadTemplate" />
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
        <el-button type="primary"
                   @click="submitFileForm">ç¡® å®š</el-button>
        <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
      </div>
    </template>
@@ -16,100 +27,102 @@
</template>
<script setup>
import { reactive, computed } from "vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import { ElMessage } from "element-plus";
import { downloadProductModelImportTemplate } from "@/api/basicData/product.js";
  import { reactive, computed } from "vue";
  import { getToken } from "@/utils/auth.js";
  import { FileUpload } from "@/components/Upload";
  import { ElMessage } from "element-plus";
  import { downloadProductModelImportTemplate } from "@/api/basicData/newProduct.js";
defineOptions({
  name: "产品维护导入",
});
  defineOptions({
    name: "产品维护导入",
  });
const props = defineProps({
  productId: { type: [String, Number], default: "" },
});
const emits = defineEmits(["uploadSuccess"]);
const fileUploadRef = ref();
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
// ä¸Šä¼ çš„地址(携带 productId å‚数,传给后端的 importProduct æŽ¥å£ï¼‰
const uploadUrl = computed(
  () =>
    import.meta.env.VITE_APP_BASE_API +
    "/basic/product/import" +
    (props.productId ? `?productId=${props.productId}` : "")
);
// ç‚¹å‡»å¯¼å…¥
const handleImport = () => {
  if (!props.productId) {
    ElMessage({ message: "请先选择产品", type: "warning" });
    return;
  }
  upload.open = true;
  upload.title = "产品导入";
};
  const props = defineProps({
    productId: { type: [String, Number], default: "" },
  });
  const emits = defineEmits(["uploadSuccess"]);
  const fileUploadRef = ref();
  const upload = reactive({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
    open: false,
    // å¼¹å‡ºå±‚标题(供应商导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
  });
  // ä¸Šä¼ çš„地址(携带 productId å‚数,传给后端的 importProduct æŽ¥å£ï¼‰
  const uploadUrl = computed(
    () =>
      import.meta.env.VITE_APP_BASE_API +
      "/productMaterialSku/import" +
      (props.productId ? `?materialId=${props.productId}` : "")
  );
  // ç‚¹å‡»å¯¼å…¥
  const handleImport = () => {
    if (!props.productId) {
      ElMessage({ message: "请先选择产品", type: "warning" });
      return;
    }
    upload.open = true;
    upload.title = "产品规格导入";
  };
const submitFileForm = () => {
  fileUploadRef.value.uploadApi();
};
  const submitFileForm = () => {
    fileUploadRef.value.uploadApi();
  };
// å…³é—­å¼¹çª—时清除已选文件
const handleDialogClose = () => {
  fileUploadRef.value?.clearFiles?.();
};
  // å…³é—­å¼¹çª—时清除已选文件
  const handleDialogClose = () => {
    fileUploadRef.value?.clearFiles?.();
  };
const handleFileSuccess = (response) => {
  const { code, msg } = response;
  if (code == 200) {
    ElMessage({ message: msg || "导入成功", type: "success" });
    upload.open = false;
    emits("uploadSuccess");
  } else {
    ElMessage({ message: msg, type: "error" });
  }
};
  const handleFileSuccess = response => {
    const { code, msg } = response;
    if (code == 200) {
      ElMessage({ message: msg || "导入成功", type: "success" });
      upload.open = false;
      emits("uploadSuccess");
    } else {
      ElMessage({ message: msg, type: "error" });
    }
  };
// ä¸‹è½½ Excel å¯¼å…¥æ¨¡æ¿
const handleDownloadTemplate = () => {
  downloadProductModelImportTemplate()
    .then((blobData) => {
      const blob =
        blobData instanceof Blob
          ? blobData
          : new Blob([blobData], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = "产品导入模板.xlsx";
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      window.URL.revokeObjectURL(url);
      ElMessage({ message: "模板下载成功", type: "success" });
    })
    .catch(() => {
      ElMessage({ message: "模板下载失败", type: "error" });
    });
};
  // ä¸‹è½½ Excel å¯¼å…¥æ¨¡æ¿
  const handleDownloadTemplate = () => {
    downloadProductModelImportTemplate()
      .then(blobData => {
        const blob =
          blobData instanceof Blob
            ? blobData
            : new Blob([blobData], {
                type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              });
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        link.download = "产品规格导入模板.xlsx";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
        ElMessage({ message: "模板下载成功", type: "success" });
      })
      .catch(() => {
        ElMessage({ message: "模板下载失败", type: "error" });
      });
  };
</script>
<style scoped>
.import-tip {
  margin-top: 12px;
  font-size: 12px;
  color: var(--el-text-color-secondary);
}
  .import-tip {
    margin-top: 12px;
    font-size: 12px;
    color: var(--el-text-color-secondary);
  }
.import-tip .el-button {
  margin-left: 8px;
}
  .import-tip .el-button {
    margin-left: 8px;
  }
</style>
src/views/basicData/product/index.vue
@@ -2,64 +2,69 @@
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
          v-model="search"
          style="width: 210px"
          placeholder="输入关键字进行搜索"
          @change="searchFilter"
          @clear="searchFilter"
          clearable
          prefix-icon="Search"
        />
        <el-button
          type="primary"
          @click="openProDia('addOne')"
          style="margin-left: 10px"
          >新增产品大类</el-button
        >
        <el-input v-model="search"
                  style="width: 210px"
                  placeholder="输入关键字进行搜索"
                  @change="searchFilter"
                  @clear="searchFilter"
                  clearable
                  prefix-icon="Search" />
        <el-button type="primary"
                   @click="openProDia1('addOne')"
                   style="margin-left: 10px">新增产品大类</el-button>
      </div>
      <div ref="containerRef">
        <el-tree
          ref="tree"
          v-loading="treeLoad"
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          :default-expanded-keys="expandedKeys"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
          node-key="id"
          class="product-tree-scroll"
          style="height: calc(100vh - 190px); overflow-y: auto"
        >
        <el-tree ref="tree"
                 v-loading="treeLoad"
                 :data="list"
                 @node-click="handleNodeClick"
                 :expand-on-click-node="false"
                 :default-expanded-keys="expandedKeys"
                 :filter-node-method="filterNode"
                 :props="{ children: 'children', label: 'label' }"
                 highlight-current
                 node-key="id"
                 class="product-tree-scroll">
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                <el-icon class="tree-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                <span class="tree-node-label">{{ data.label }}</span>
              </span>
              <div>
                <el-button
                  type="primary"
                  link
                  @click="openProDia('edit', data)"
                >
              <div v-if="data.isLeaf">
                <el-button type="primary"
                           link
                           @click="openProDia('edit', data)">
                  ç¼–辑
                </el-button>
                <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3">
                <el-button v-if="!node.childNodes.length"
                           style="margin-left: 4px"
                           type="danger"
                           link
                           @click="remove(node, data)">
                  åˆ é™¤
                </el-button>
              </div>
              <div v-else>
                <!-- å¤§ç±» -->
                <el-button type="primary"
                           link
                           @click="openProDia1('edit', data)">
                  ç¼–辑
                </el-button>
                <el-button type="primary"
                           link
                           @click="openProDia('add', data)">
                  æ·»åŠ äº§å“
                </el-button>
                <el-button
                  v-if="!node.childNodes.length"
                  style="margin-left: 4px"
                  type="danger"
                  link
                  @click="remove(node, data)"
                >
                <el-button style="margin-left: 4px"
                           type="danger"
                           v-if="!node.childNodes.length"
                           link
                           @click="remove1(node, data)">
                  åˆ é™¤
                </el-button>
              </div>
@@ -69,104 +74,189 @@
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px" v-if="isShowButton">
        <el-button type="primary" @click="openModelDia('add')">
      <div style="margin-bottom: 10px"
           v-if="isShowButton">
        <el-button type="primary"
                   @click="openModelDia('add')">
          æ–°å¢žè§„格型号
        </el-button>
        <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" />
        <el-button
          type="danger"
          @click="handleDelete"
          style="margin-left: 10px"
          plain
        >
        <ImportExcel :product-id="currentId"
                     @uploadSuccess="getModelList" />
        <el-button type="danger"
                   @click="handleDelete"
                   style="margin-left: 10px"
                   plain>
          åˆ é™¤
        </el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      ></PIMTable>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :isSelection="true"
                :isShowPagination="false"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"></PIMTable>
    </div>
    <el-dialog v-model="productDia" title="产品" width="400px" @keydown.enter.prevent>
      <el-form
        :model="form"
        label-width="140px"
        label-position="top"
        :rules="rules"
        ref="formRef"
      >
    <el-dialog v-model="productDia"
               title="产品"
               width="400px"
               @keydown.enter.prevent>
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:" prop="productName">
              <el-input
                v-model="form.productName"
                placeholder="请输入产品名称"
                maxlength="20"
                show-word-limit
                clearable
                @keydown.enter.prevent
              />
            <el-form-item label="产品名称:"
                          prop="materialName">
              <el-input v-model="form.materialName"
                        placeholder="请输入产品名称"
                        maxlength="20"
                        show-word-limit
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="存货类别:"
                          prop="inventoryCategoryId">
              <el-select v-model="form.inventoryCategoryId"
                         placeholder="请选择存货类别"
                         clearable
                         style="width: 100%">
                <el-option v-for="item in inventoryCategoryList"
                           :key="item.id"
                           :label="item.configName"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="物料类型:"
                          prop="materialTypeId">
              <el-select v-model="form.materialTypeId"
                         placeholder="请选择物料类型"
                         clearable
                         style="width: 100%">
                <el-option v-for="item in materialTypeList"
                           :key="item.id"
                           :label="item.configName"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="基本单位:"
                          prop="baseUnit">
              <el-input v-model="form.baseUnit"
                        placeholder="请输入基本单位"
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注:">
              <el-input v-model="form.remark"
                        placeholder="请输入备注"
                        type="textarea"
                        :rows="3"
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeProDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog
      v-model="modelDia"
      title="规格型号"
      width="400px"
      @close="closeModelDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="modelForm"
        label-width="140px"
        label-position="top"
        :rules="modelRules"
        ref="modelFormRef"
      >
    <el-dialog v-model="modelDia"
               title="规格型号"
               width="400px"
               @close="closeModelDia"
               @keydown.enter.prevent>
      <el-form :model="modelForm"
               label-width="140px"
               label-position="top"
               :rules="modelRules"
               ref="modelFormRef">
        <el-row>
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="model">
              <el-input
                v-model="modelForm.model"
                placeholder="请输入规格型号"
                clearable
                @keydown.enter.prevent
              />
            <el-form-item label="规格型号:"
                          prop="specification">
              <el-input v-model="modelForm.specification"
                        placeholder="请输入规格型号"
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="单位:" prop="unit">
              <el-input
                v-model="modelForm.unit"
                placeholder="请输入单位"
                clearable
                @keydown.enter.prevent
              />
            <el-form-item label="供应方式:"
                          prop="supplyType">
              <el-select v-model="modelForm.supplyType"
                         placeholder="请选择供应方式"
                         clearable
                         style="width: 100%">
                <el-option label="自制"
                           value="自制" />
                <el-option label="外购"
                           value="外购" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button type="primary"
                     @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="configDia"
               title="产品大类"
               width="400px"
               @keydown.enter.prevent>
      <el-form :model="configForm"
               label-width="140px"
               label-position="top"
               :rules="configRules"
               ref="configFormRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="配置名称:"
                          prop="configName">
              <el-input v-model="configForm.configName"
                        placeholder="请输入配置名称"
                        maxlength="20"
                        show-word-limit
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitConfigForm">确认</el-button>
          <el-button @click="closeConfigDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -174,359 +264,686 @@
</template>
<script setup>
import { ref } from "vue";
import { ElMessageBox } from "element-plus";
import {
  addOrEditProduct,
  addOrEditProductModel,
  delProduct,
  delProductModel,
  modelListPage,
  productTreeList,
} from "@/api/basicData/product.js";
import ImportExcel from "./ImportExcel/index.vue";
  import { ref } from "vue";
  import { ElMessageBox } from "element-plus";
  import {
    addOrEditProduct,
    updateOrEditProduct,
    getinventoryCategoryList,
    getmaterialTypeList,
    addOrEditProductModel,
    updateOrEditProductModel,
    delProduct,
    delProductModel,
    modelListPage,
    productTreeList,
    addOrEditProductConfig,
    updateOrEditProductConfig,
    delProductConfig,
  } from "@/api/basicData/newProduct.js";
  import ImportExcel from "./ImportExcel/index.vue";
const { proxy } = getCurrentInstance();
const tree = ref(null);
const containerRef = ref(null);
  const { proxy } = getCurrentInstance();
  const tree = ref(null);
  const containerRef = ref(null);
const productDia = ref(false);
const modelDia = ref(false);
const modelOperationType = ref("");
const search = ref("");
const currentId = ref("");
const currentParentId = ref("");
const operationType = ref("");
const treeLoad = ref(false);
const list = ref([]);
const expandedKeys = ref([]);
const tableColumn = ref([
  {
    label: "规格型号",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openModelDia("edit", row);
  const productDia = ref(false);
  const modelDia = ref(false);
  const configDia = ref(false);
  const modelOperationType = ref("");
  const configOperationType = ref("");
  const search = ref("");
  const currentId = ref("");
  const currentParentId = ref("");
  const operationType = ref("");
  const treeLoad = ref(false);
  const list = ref([]);
  const expandedKeys = ref([]);
  const inventoryCategoryList = ref([]);
  const materialTypeList = ref([]);
  // èŽ·å–å­˜è´§ç±»åˆ«åˆ—è¡¨
  const getInventoryCategoryList = () => {
    getinventoryCategoryList()
      .then(res => {
        inventoryCategoryList.value = res.data.map(item => ({
          id: item.id,
          configName: item.configName,
        }));
      })
      .catch(err => {
        console.error("获取存货类别失败:", err);
      });
  };
  // èŽ·å–ç‰©æ–™ç±»åž‹åˆ—è¡¨
  const getMaterialTypeList = () => {
    getmaterialTypeList()
      .then(res => {
        materialTypeList.value = res.data.map(item => ({
          id: item.id,
          configName: item.configName,
        }));
      })
      .catch(err => {
        console.error("获取物料类型失败:", err);
      });
  };
  const tableColumn = ref([
    {
      label: "规格型号",
      prop: "materialName",
    },
    {
      label: "规格",
      prop: "specification",
    },
    {
      label: "单位",
      prop: "baseUnit",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            openModelDia("edit", row);
          },
        },
      },
    ],
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const isShowButton = ref(false);
const selectedRows = ref([]);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  form: {
    productName: "",
  },
  rules: {
    productName: [
      { required: true, message: "请输入", trigger: "blur" },
      { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
    ],
  },
  modelForm: {
    model: "",
    unit: "",
  },
  modelRules: {
    model: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const { form, rules, modelForm, modelRules } = toRefs(data);
// æŸ¥è¯¢äº§å“æ ‘
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList()
    .then((res) => {
      list.value = res;
      list.value.forEach((a) => {
        expandedKeys.value.push(a.label);
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const isShowButton = ref(false);
  const selectedRows = ref([]);
  const data = reactive({
    form: {
      materialTypeId: null,
      inventoryCategoryId: null,
      materialName: "",
      baseUnit: "",
      remark: "",
    },
    rules: {
      materialName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
      ],
      inventoryCategoryId: [
        { required: true, message: "请选择", trigger: "change" },
      ],
      baseUnit: [{ required: true, message: "请输入", trigger: "blur" }],
    },
    modelForm: {
      specification: "",
      supplyType: "",
      id: null,
    },
    modelRules: {
      specification: [{ required: true, message: "请输入", trigger: "blur" }],
      supplyType: [{ required: true, message: "请选择", trigger: "change" }],
    },
    configForm: {
      configName: "",
      type: 1,
      id: null,
    },
    configRules: {
      configName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "配置名称不能超过20个字符", trigger: "blur" },
      ],
    },
  });
  const { form, rules, modelForm, modelRules, configForm, configRules } =
    toRefs(data);
  // æŸ¥è¯¢äº§å“æ ‘
  const getProductTreeList = () => {
    treeLoad.value = true;
    productTreeList()
      .then(res => {
        // è½¬æ¢æ–°çš„æ•°æ®æ ¼å¼
        const newList = [];
        expandedKeys.value = [];
        for (const category of res.data) {
          // æ·»åŠ åˆ†ç±»èŠ‚ç‚¹
          const categoryNode = {
            label: category.configName,
            id: category.configId,
            isLeaf: false,
            children: category.materialList.map(item => ({
              id: item.id,
              isLeaf: true,
              label: item.materialName,
              inventoryCategoryId: item.inventoryCategoryId,
              materialTypeId: item.materialTypeId,
              remark: item.remark,
              baseUnit: item.baseUnit,
            })),
          };
          newList.push(categoryNode);
          expandedKeys.value.push(category.configName);
        }
        list.value = newList;
        treeLoad.value = false;
      })
      .catch(err => {
        treeLoad.value = false;
      });
      treeLoad.value = false;
    })
    .catch((err) => {
      treeLoad.value = false;
    });
};
// è¿‡æ»¤äº§å“æ ‘
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
};
// æ‰“开产品弹框
const openProDia = (type, data) => {
  operationType.value = type;
  productDia.value = true;
  form.value.productName = "";
  if (type === "edit") {
    form.value.productName = data.productName;
  }
};
// æ‰“开规格型号弹框
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  modelForm.value.model = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  if (type === "edit") {
    modelForm.value = { ...data };
  }
};
// æäº¤äº§å“åç§°ä¿®æ”¹
const submitForm = () => {
  proxy.$refs.formRef.validate((valid) => {
    if (valid) {
      if (operationType.value === "add") {
        form.value.parentId = currentId.value;
        form.value.id = "";
      } else if (operationType.value === "addOne") {
        form.value.id = "";
        form.value.parentId = "";
      } else {
        form.value.id = currentId.value;
        form.value.parentId = "";
  };
  // è¿‡æ»¤äº§å“æ ‘
  const searchFilter = () => {
    proxy.$refs.tree.filter(search.value);
  };
  // æ‰“开产品弹框
  const openProDia = (type, data) => {
    operationType.value = type;
    productDia.value = true;
    // é‡ç½®è¡¨å•
    form.value = {
      materialName: "",
      inventoryCategoryId: null,
      baseUnit: "",
      remark: "",
      materialTypeId: null,
    };
    console.log(data);
    if (type === "edit" && data) {
      // ç¼–辑模式,回填数据
      form.value.materialName = data.label || "";
      form.value.inventoryCategoryId = data.inventoryCategoryId || null;
      form.value.baseUnit = data.baseUnit || "";
      form.value.remark = data.remark || "";
      form.value.materialTypeId = data.materialTypeId || null;
      form.value.id = data.id || null;
    } else {
      form.value.materialTypeId = data.id || null;
    }
  };
  // æ‰“开产品大类弹框
  const openProDia1 = (type, data) => {
    configOperationType.value = type;
    configDia.value = true;
    // é‡ç½®è¡¨å•
    configForm.value = {
      configName: "",
      type: 1,
      id: null,
    };
    if (type === "edit" && data) {
      // ç¼–辑模式,回填数据
      configForm.value.configName = data.label || "";
      configForm.value.id = data.id || null;
    }
  };
  // æ‰“开规格型号弹框
  const openModelDia = (type, data) => {
    modelOperationType.value = type;
    modelDia.value = true;
    // é‡ç½®æ‰€æœ‰å­—段
    modelForm.value.specification = "";
    modelForm.value.supplyType = "";
    modelForm.value.id = null;
    if (type === "edit" && data) {
      // ç¼–辑模式,回填数据
      modelForm.value.specification = data.specification || "";
      modelForm.value.supplyType = data.supplyType || "";
      modelForm.value.id = data.skuId || null;
    }
  };
  // æäº¤äº§å“åç§°ä¿®æ”¹
  const submitForm = () => {
    proxy.$refs.formRef.validate(valid => {
      if (valid) {
        // æž„建提交参数
        // const params = {
        //   materialTypeId: null,
        //   inventoryCategoryId: form.value.inventoryCategoryId,
        //   materialName: form.value.materialName,
        //   baseUnit: form.value.baseUnit,
        //   remark: form.value.remark,
        // };
        // if (operationType.value === "add") {
        //   // æ·»åŠ å­çº§ï¼Œä¼ çˆ¶çº§çš„id作为 materialTypeId
        //   params.materialTypeId = currentId.value;
        // } else if (operationType.value === "addOne") {
        //   // æ·»åŠ ä¸€çº§ï¼ŒmaterialTypeId ä¸º null
        //   params.materialTypeId = null;
        // } else {
        //   // ç¼–辑,传当前id作为 materialTypeId
        //   params.materialTypeId = currentId.value;
        // }
        console.log(form.value);
        console.log(operationType.value);
        if (operationType.value != "edit") {
          addOrEditProduct(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeProDia();
            getProductTreeList();
          });
        } else {
          updateOrEditProduct(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeProDia();
            getProductTreeList();
          });
        }
      }
      addOrEditProduct(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeProDia();
        getProductTreeList();
      });
    }
  });
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProDia = () => {
  proxy.$refs.formRef.resetFields();
  productDia.value = false;
};
// åˆ é™¤äº§å“
const remove = (node, data) => {
  let ids = [];
  ids.push(data.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delProduct(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getProductTreeList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// é€‰æ‹©äº§å“
const handleNodeClick = (val, node, el) => {
  // åˆ¤æ–­æ˜¯å¦ä¸ºå¶å­èŠ‚ç‚¹
  isShowButton.value = !(val.children && val.children.length > 0);
  // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  getModelList();
};
// æäº¤è§„格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate((valid) => {
    if (valid) {
      modelForm.value.productId = currentId.value;
      addOrEditProductModel(modelForm.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeModelDia();
        getModelList();
      });
    }
  });
};
// å…³é—­åž‹å·å¼¹æ¡†
const closeModelDia = () => {
  proxy.$refs.modelFormRef.resetFields();
  modelDia.value = false;
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æŸ¥è¯¢è§„格型号
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true;
  modelListPage({
    id: currentId.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    console.log("res", res);
    tableData.value = res.records;
    page.total = res.total;
    tableLoading.value = false;
  });
};
// åˆ é™¤è§„格型号
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delProductModel(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getModelList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
  };
  // å…³é—­äº§å“å¼¹æ¡†
  const closeProDia = () => {
    proxy.$refs.formRef.resetFields();
    // æ‰‹åŠ¨é‡ç½®éžè¡¨å•å­—æ®µ
    form.value.materialTypeId = null;
    form.value.remark = "";
    productDia.value = false;
  };
  // æäº¤äº§å“å¤§ç±»ä¿®æ”¹
  const submitConfigForm = () => {
    proxy.$refs.configFormRef.validate(valid => {
      if (valid) {
        const params = {
          configName: configForm.value.configName,
          type: 1,
        };
        if (configOperationType.value === "edit") {
          params.id = configForm.value.id;
          updateOrEditProductConfig(params).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeConfigDia();
            getProductTreeList();
          });
        } else {
          addOrEditProductConfig(params).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeConfigDia();
            getProductTreeList();
          });
        }
      }
    });
};
// è°ƒç”¨tree过滤方法 ä¸­æ–‡è‹±è¿‡æ»¤
const filterNode = (value, data, node) => {
  if (!value) {
    //如果数据为空,则返回true,显示所有的数据项
    return true;
  }
  // æŸ¥è¯¢åˆ—表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase();
  return chooseNode(val, data, node); // è°ƒç”¨è¿‡æ»¤äºŒå±‚方法
};
// è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹ (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true;
  }
  const level = node.level;
  // å¦‚果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false;
  }
  // å…ˆå–当前节点的父节点
  let parentData = node.parent;
  // éåŽ†å½“å‰èŠ‚ç‚¹çš„çˆ¶èŠ‚ç‚¹
  let index = 0;
  while (index < level - 1) {
    // å¦‚果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
  };
  // å…³é—­äº§å“å¤§ç±»å¼¹æ¡†
  const closeConfigDia = () => {
    proxy.$refs.configFormRef.resetFields();
    configDia.value = false;
  };
  // åˆ é™¤äº§å“å¤§ç±»
  const remove1 = (node, data) => {
    let ids = [];
    ids.push(data.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        tableLoading.value = true;
        delProductConfig(ids)
          .then(res => {
            proxy.$modal.msgSuccess("删除成功");
            getProductTreeList();
          })
          .finally(() => {
            tableLoading.value = false;
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // åˆ é™¤äº§å“
  const remove = (node, data) => {
    let ids = [];
    ids.push(data.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        tableLoading.value = true;
        delProduct(ids)
          .then(res => {
            proxy.$modal.msgSuccess("删除成功");
            getProductTreeList();
          })
          .finally(() => {
            tableLoading.value = false;
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // é€‰æ‹©äº§å“
  const handleNodeClick = (val, node, el) => {
    // ç‚¹å‡»éžå¶å­èŠ‚ç‚¹æ—¶ï¼Œä¸æ‰§è¡Œä»¥ä¸‹é€»è¾‘
    if (!val.isLeaf) {
      return;
    }
    // åˆ¤æ–­æ˜¯å¦ä¸ºå¶å­èŠ‚ç‚¹
    isShowButton.value = !(val.children && val.children.length > 0);
    // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
    currentId.value = val.id;
    currentParentId.value = val.parentId;
    getModelList();
  };
  // æäº¤è§„格型号修改
  const submitModelForm = () => {
    proxy.$refs.modelFormRef.validate(valid => {
      if (valid) {
        // æž„建提交参数
        const params = {
          materialId: currentId.value,
          specification: modelForm.value.specification,
          supplyType: modelForm.value.supplyType,
        };
        if (modelOperationType.value === "add") {
          // æ·»åŠ è§„æ ¼åž‹å·
          addOrEditProductModel(params).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeModelDia();
            getModelList();
          });
        } else if (modelOperationType.value === "edit") {
          // ä¿®æ”¹è§„格型号
          params.id = modelForm.value.id;
          updateOrEditProductModel(params).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeModelDia();
            getModelList();
          });
        }
      }
    });
  };
  // å…³é—­åž‹å·å¼¹æ¡†
  const closeModelDia = () => {
    proxy.$refs.modelFormRef.resetFields();
    modelDia.value = false;
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // æŸ¥è¯¢è§„格型号
  const getModelList = () => {
    if (!currentId.value) {
      return;
    }
    tableLoading.value = true;
    modelListPage({
      materialId: currentId.value,
    }).then(res => {
      console.log("res", res);
      tableData.value = res.data;
      tableLoading.value = false;
    });
  };
  // åˆ é™¤è§„格型号
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.skuId);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        tableLoading.value = true;
        delProductModel(ids)
          .then(res => {
            proxy.$modal.msgSuccess("删除成功");
            getModelList();
          })
          .finally(() => {
            tableLoading.value = false;
          });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // è°ƒç”¨tree过滤方法 ä¸­æ–‡è‹±è¿‡æ»¤
  const filterNode = (value, data, node) => {
    if (!value) {
      //如果数据为空,则返回true,显示所有的数据项
      return true;
    }
    // å¦åˆ™çš„话再往上一层做匹配
    parentData = parentData.parent;
    index++;
  }
  // æ²¡åŒ¹é…åˆ°è¿”回false
  return false;
};
getProductTreeList();
    // æŸ¥è¯¢åˆ—表是否有匹配数据,将值小写,匹配英文数据
    let val = value.toLowerCase();
    return chooseNode(val, data, node); // è°ƒç”¨è¿‡æ»¤äºŒå±‚方法
  };
  // è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹ (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
  const chooseNode = (value, data, node) => {
    if (data.label.indexOf(value) !== -1) {
      return true;
    }
    const level = node.level;
    // å¦‚果传入的节点本身就是一级节点就不用校验了
    if (level === 1) {
      return false;
    }
    // å…ˆå–当前节点的父节点
    let parentData = node.parent;
    // éåŽ†å½“å‰èŠ‚ç‚¹çš„çˆ¶èŠ‚ç‚¹
    let index = 0;
    while (index < level - 1) {
      // å¦‚果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
      if (parentData.data.label.indexOf(value) !== -1) {
        return true;
      }
      // å¦åˆ™çš„话再往上一层做匹配
      parentData = parentData.parent;
      index++;
    }
    // æ²¡åŒ¹é…åˆ°è¿”回false
    return false;
  };
  // é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
  getInventoryCategoryList();
  getMaterialTypeList();
  getProductTreeList();
</script>
<style scoped>
.product-view {
  display: flex;
}
.left {
  width: 450px;
  min-width: 450px;
  padding: 16px;
  background: #ffffff;
}
.right {
  flex: 1;
  min-width: 0;
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
.tree-node-content {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 100%;
  overflow: hidden;
}
.tree-node-content .orange-icon {
  flex-shrink: 0;
}
.tree-node-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* å›¾æ ‡ä¸Žæ–‡å­—之间加点间距 */
}
.product-tree-scroll {
  scrollbar-width: thin;
  scrollbar-color: #c0c4cc #f5f7fa;
}
.product-tree-scroll::-webkit-scrollbar {
  width: 8px;
}
.product-tree-scroll::-webkit-scrollbar-track {
  background: #f5f7fa;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb {
  background: #c0c4cc;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb:hover {
  background: #909399;
}
  .product-view {
    display: flex;
    min-height: 100vh;
    background-color: #f5f7fa;
    padding: 20px;
    gap: 20px;
  }
  .left {
    width: 450px;
    min-width: 450px;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  .left > div:first-child {
    padding: 16px;
    border-bottom: 1px solid #e4e7ed;
    background-color: #fafafa;
  }
  .left > div:last-child {
    flex: 1;
    overflow: hidden;
  }
  .right {
    flex: 1;
    min-width: 0;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  .right > div:first-child {
    padding: 16px;
    border-bottom: 1px solid #e4e7ed;
    background-color: #fafafa;
  }
  .right > div:last-child {
    flex: 1;
    overflow: auto;
    padding: 16px;
  }
  .custom-tree-node {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    padding: 8px 12px;
    margin: 2px 0;
    border-radius: 4px;
    transition: all 0.3s ease;
  }
  .custom-tree-node:hover {
    background-color: #f5f7fa;
  }
  .tree-node-content {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    height: 100%;
    overflow: hidden;
  }
  .tree-node-content .tree-icon {
    flex-shrink: 0;
  }
  .tree-node-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    color: #303133;
  }
  .tree-icon {
    color: #409eff;
    font-size: 18px;
    margin-right: 10px;
    transition: color 0.3s ease;
  }
  .custom-tree-node:hover .tree-icon {
    color: #66b1ff;
  }
  .product-tree-scroll {
    height: calc(100vh - 240px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: #c0c4cc #f5f7fa;
  }
  .product-tree-scroll::-webkit-scrollbar {
    width: 6px;
  }
  .product-tree-scroll::-webkit-scrollbar-track {
    background: #f5f7fa;
    border-radius: 3px;
  }
  .product-tree-scroll::-webkit-scrollbar-thumb {
    background: #c0c4cc;
    border-radius: 3px;
  }
  .product-tree-scroll::-webkit-scrollbar-thumb:hover {
    background: #909399;
  }
  .el-button {
    border-radius: 4px;
    font-size: 13px;
  }
  .el-button--primary {
    background-color: #fff;
    border-color: #409eff;
    color: #409eff;
  }
  .el-button--primary.is-link {
    background-color: transparent;
    border-color: transparent;
    color: #409eff;
  }
  .el-button--primary.is-link:hover {
    background-color: transparent;
    border-color: transparent;
    color: #0033ff;
  }
  .el-button--primary:hover {
    background-color: #e3f1ff;
    border-color: #66b1ff;
  }
  .el-input {
    border-radius: 4px;
  }
  .el-input__wrapper {
    border-radius: 4px;
  }
  .el-dialog {
    border-radius: 8px;
  }
  .el-dialog__header {
    border-bottom: 1px solid #e4e7ed;
    padding: 16px 20px;
  }
  .el-dialog__title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .el-dialog__body {
    padding: 20px;
  }
  .el-form-item {
    margin-bottom: 16px;
  }
  .el-form-item__label {
    font-weight: 500;
    color: #606266;
  }
  .dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 16px 20px;
    border-top: 1px solid #e4e7ed;
    background-color: #fafafa;
  }
  .PIMTable {
    border-radius: 4px;
    overflow: hidden;
  }
  .el-table {
    border-radius: 4px;
  }
  .el-table th {
    background-color: #fafafa;
    font-weight: 600;
    color: #303133;
  }
  .el-table tr:hover {
    background-color: #f5f7fa;
  }
</style>
src/views/basicData/product/xxx.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,615 @@
{
    "msg": "操作成功",
    "code": 200,
    "data": [
        {
            "configId": 19,
            "configName": "成品",
            "materialList": [
                {
                    "id": 11,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "板材",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 13,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "MU20烧结煤矸石实心砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 14,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "水泥标砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 15,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "烧结煤矸石页岩实心砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 16,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "水泥三角砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 17,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "水泥方砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 18,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "烧结煤矸石页岩空心砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 19,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "烧结煤矸石页岩砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 20,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "标砖",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 23,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "加气板",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        },
        {
            "configId": 27,
            "configName": "生产物耗",
            "materialList": [
                {
                    "id": 43,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "防冻液",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 60,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "醇酸调和漆",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 64,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "清洗剂",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 106,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "灰斗车",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 116,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "打包带(手工带)",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 117,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "稀释剂",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 118,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "木托盘",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 310,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "三乙醇胺",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 313,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "乙炔",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 314,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "透平油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 315,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "电解粉",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 316,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "打包带",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 317,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "钢球",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 318,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "对讲机",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 319,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "钢锻",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 320,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "抗磨液压油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 321,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "黄油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 322,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "缝纫机油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 323,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "机油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 324,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "柴油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 325,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "昆仑原包M320开齿轮油原",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 326,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "昆仑CKD320中负荷齿轮油原",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 327,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "丙烷",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 328,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "昆仑CKC320中负荷齿轮油原",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 329,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "昆仑CKC150工业闭式齿轮油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 330,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "氧气",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 331,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "昆仑46#抗磨液压油",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 332,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "石蜡",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 335,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "脱模剂",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        },
        {
            "configId": 30,
            "configName": "沙加砌块",
            "materialList": [
                {
                    "id": 139,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "沙加砌块",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        },
        {
            "configId": 31,
            "configName": "砌块",
            "materialList": [
                {
                    "id": 12,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "砌块",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        },
        {
            "configId": 35,
            "configName": "辅助材料",
            "materialList": [
                {
                    "id": 22,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "氧化镁",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 486,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "卡扣",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 487,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "防腐剂",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 489,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "冷拔丝",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        },
        {
            "configId": 36,
            "configName": "原材料",
            "materialList": [
                {
                    "id": 41,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "生石灰",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 42,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "æ°´",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 488,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "硅砂",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 490,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "æ°´æ³¥",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 491,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "铝粉",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 492,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "粉煤灰",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                },
                {
                    "id": 493,
                    "tenantId": null,
                    "materialTypeId": null,
                    "inventoryCategoryId": null,
                    "materialName": "石膏",
                    "baseUnit": null,
                    "remark": null,
                    "createTime": null,
                    "updateTime": null
                }
            ]
        }
    ]
}
src/views/costAccounting/energyCosts/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1135 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="统计维度:">
          <el-radio-group v-model="statisticsType"
                          @change="handleTypeChange">
            <el-radio-button label="day">按日统计</el-radio-button>
            <el-radio-button label="month">按月统计</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="能耗类型:">
          <el-select v-model="searchForm.energyType"
                     placeholder="全部"
                     clearable
                     style="width: 140px;"
                     @change="handleQuery">
            <el-option label="全部"
                       value="全部" />
            <el-option label="æ°´"
                       value="æ°´" />
            <el-option label="电"
                       value="电" />
            <el-option label="气"
                       value="气" />
          </el-select>
        </el-form-item>
        <el-form-item label="能耗用途:">
          <el-select v-model="searchForm.energyPurpose"
                     placeholder="全部"
                     clearable
                     style="width: 140px;"
                     @change="handleQuery">
            <el-option label="全部"
                       value="全部" />
            <el-option label="生产"
                       value="生产" />
            <el-option label="办公"
                       value="办公" />
          </el-select>
        </el-form-item>
        <el-form-item label="时间范围:">
          <el-date-picker v-if="statisticsType === 'day'"
                          v-model="searchForm.dateRange"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                          value-format="YYYY-MM-DD"
                          style="width: 240px;"
                          @change="handleQuery" />
          <el-date-picker v-else
                          v-model="searchForm.monthRange"
                          type="monthrange"
                          range-separator="至"
                          start-placeholder="开始月份"
                          end-placeholder="结束月份"
                          value-format="YYYY-MM"
                          style="width: 240px;"
                          @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">查询</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="success"
                   @click="handleExport">导出报表</el-button>
      </div>
    </div>
    <!-- ç»Ÿè®¡æ¦‚览卡片 -->
    <div class="statistics-overview">
      <h2 class="section-header">
        <el-icon class="header-icon">
          <DataLine />
        </el-icon>
        èƒ½è€—成本概览
      </h2>
      <el-row :gutter="20">
        <el-col :span="6">
          <div class="overview-card blue-card">
            <div class="overview-icon blue-icon">
              <el-icon>
                <Money />
              </el-icon>
            </div>
            <div class="overview-info">
              <div class="overview-label">总能耗成本</div>
              <div class="overview-value">Â¥{{ overview.totalCost }}</div>
            </div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="overview-card green-card">
            <div class="overview-icon green-icon">
              <el-icon>
                <DataLine />
              </el-icon>
            </div>
            <div class="overview-info">
              <div class="overview-label">生产能耗成本</div>
              <div class="overview-value">Â¥{{ overview.productionCost }}</div>
            </div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="overview-card purple-card">
            <div class="overview-icon purple-icon">
              <el-icon>
                <TrendCharts />
              </el-icon>
            </div>
            <div class="overview-info">
              <div class="overview-label">办公能耗成本</div>
              <div class="overview-value">Â¥{{ overview.officeCost }}</div>
            </div>
          </div>
        </el-col>
        <el-col :span="6">
          <div class="overview-card gray-card">
            <div class="overview-icon gray-icon">
              <el-icon>
                <Histogram />
              </el-icon>
            </div>
            <div class="overview-info">
              <div class="overview-label">平均能耗成本</div>
              <div class="overview-value">Â¥{{ overview.avgCost }} <span class="unit">/{{ statisticsType === 'day' ? '日' : '月' }}</span></div>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="charts-container">
      <h2 class="section-header">
        <el-icon class="header-icon">
          <Histogram />
        </el-icon>
        èƒ½è€—成本分析
      </h2>
      <el-row :gutter="20">
        <el-col :span="12">
          <div class="chart-card">
            <div class="chart-title">能耗成本趋势</div>
            <div ref="costChart"
                 class="chart-content"></div>
          </div>
        </el-col>
        <el-col :span="12">
          <div class="chart-card">
            <div class="chart-title">能耗类型成本占比</div>
            <div ref="typeChart"
                 class="chart-content"></div>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="20"
              style="margin-top: 20px;">
        <el-col :span="12">
          <div class="chart-card">
            <div class="chart-title">能耗用途成本占比</div>
            <div ref="purposeChart"
                 class="chart-content"></div>
          </div>
        </el-col>
        <el-col :span="12">
          <div class="chart-card">
            <div class="chart-title">能耗单价对比</div>
            <div ref="priceChart"
                 class="chart-content"></div>
          </div>
        </el-col>
      </el-row>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table-section">
      <h2 class="section-header">
        <el-icon class="header-icon">
          <List />
        </el-icon>
        è¯¦ç»†æ•°æ®
      </h2>
      <el-table :data="tableData"
                v-loading="tableLoading"
                border>
        <el-table-column type="index"
                         label="序号"
                         width="60"
                         align="center" />
        <el-table-column prop="timePeriod"
                         :label="timeColumnLabel"
                         align="center" />
        <el-table-column prop="energyType"
                         label="能耗类型"
                         width="100"
                         align="center">
          <template #default="scope">
            <el-tag :type="getEnergyTypeType(scope.row.energyType)">
              {{ scope.row.energyType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="energyPurpose"
                         label="能耗用途"
                         width="100"
                         align="center">
          <template #default="scope">
            <el-tag :type="scope.row.energyPurpose === '生产' ? 'primary' : 'info'">
              {{ scope.row.energyPurpose }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="consumption"
                         label="用量"
                         align="right">
          <template #default="scope">
            <span class="consumption-value">{{ scope.row.consumption }}</span>
            <span class="consumption-unit">{{ scope.row.unit }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="price"
                         label="单价(元)"
                         align="right">
          <template #default="scope">
            <span class="price-value">{{ scope.row.price }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="cost"
                         label="成本(元)"
                         align="right"
                         fixed="right">
          <template #default="scope">
            <span class="cost-value">Â¥{{ scope.row.cost }}</span>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- åˆ†é¡µ -->
    <div class="pagination-container">
      <el-pagination v-model:current-page="page.current"
                     v-model:page-size="page.size"
                     :page-sizes="[10, 20, 50, 100]"
                     :total="page.total"
                     layout="total, sizes, prev, pager, next, jumper"
                     @size-change="handleSizeChange"
                     @current-change="handleCurrentChange" />
    </div>
  </div>
</template>
<script setup>
  import { ref, reactive, onMounted, computed, nextTick } from "vue";
  import { ElMessage } from "element-plus";
  import {
    Money,
    DataLine,
    TrendCharts,
    Histogram,
    List,
  } from "@element-plus/icons-vue";
  import * as echarts from "echarts";
  import { energyCostStatistics } from "@/api/costAccounting/energyCosts";
  // ç»Ÿè®¡ç»´åº¦ï¼šday-按日,month-按月
  const statisticsType = ref("day");
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    energyType: "",
    energyPurpose: "",
    dateRange: (() => {
      // é»˜è®¤æœ€è¿‘7天
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      return [start.toISOString().split("T")[0], end.toISOString().split("T")[0]];
    })(),
    monthRange: (() => {
      // é»˜è®¤æœ€è¿‘3个月
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
    })(),
  });
  // æ—¶é—´åˆ—标签
  const timeColumnLabel = computed(() => {
    return statisticsType.value === "day" ? "日期" : "月份";
  });
  // ç»Ÿè®¡æ¦‚览
  const overview = reactive({
    totalCost: "0.00",
    productionCost: "0.00",
    officeCost: "0.00",
    avgCost: "0.00",
  });
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  const tableLoading = ref(false);
  // åˆ†é¡µ
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // å›¾è¡¨å¼•用
  const costChart = ref(null);
  const typeChart = ref(null);
  const purposeChart = ref(null);
  const priceChart = ref(null);
  // å›¾è¡¨å®žä¾‹
  let costChartInstance = null;
  let typeChartInstance = null;
  let purposeChartInstance = null;
  let priceChartInstance = null;
  // èŽ·å–èƒ½è€—ç±»åž‹æ ‡ç­¾ç±»åž‹
  const getEnergyTypeType = type => {
    const typeMap = {
      æ°´: "primary",
      ç”µ: "warning",
      æ°”: "success",
    };
    return typeMap[type] || "info";
  };
  // åˆå§‹åŒ–图表
  const initCharts = () => {
    nextTick(() => {
      // èƒ½è€—成本趋势图
      if (costChart.value) {
        costChartInstance = echarts.init(costChart.value);
        updateCostChart();
      }
      // èƒ½è€—类型成本占比图
      if (typeChart.value) {
        typeChartInstance = echarts.init(typeChart.value);
        updateTypeChart();
      }
      // èƒ½è€—用途成本占比图
      if (purposeChart.value) {
        purposeChartInstance = echarts.init(purposeChart.value);
        updatePurposeChart();
      }
      // èƒ½è€—单价对比图
      if (priceChart.value) {
        priceChartInstance = echarts.init(priceChart.value);
        updatePriceChart();
      }
    });
  };
  // æ›´æ–°èƒ½è€—成本趋势图
  const updateCostChart = () => {
    const data = tableData.value;
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      legend: {
        data: ["生产能耗成本", "办公能耗成本"],
        top: 0,
        right: 10,
        textStyle: { color: "#606266" },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "10%",
        top: "15%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: data.map(item => item.timePeriod),
        axisLabel: {
          rotate: statisticsType.value === "day" ? 45 : 0,
          color: "#606266",
        },
        axisLine: { lineStyle: { color: "#ebeef5" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "成本(元)",
        nameTextStyle: { color: "#606266" },
        axisLabel: { color: "#606266" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "#f0f2f5" } },
      },
      series: [
        {
          name: "生产能耗成本",
          type: "bar",
          data: data.map(item => (item.energyPurpose === "生产" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#409EFF" },
              { offset: 1, color: "#66b1ff" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
          animationDelay: function (idx) {
            return idx * 100;
          },
        },
        {
          name: "办公能耗成本",
          type: "bar",
          data: data.map(item => (item.energyPurpose === "办公" ? item.cost : 0)),
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#67C23A" },
              { offset: 1, color: "#85ce61" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
          animationDelay: function (idx) {
            return idx * 100 + 100;
          },
        },
      ],
      animationEasing: "elasticOut",
      animationDelayUpdate: function (idx) {
        return idx * 5;
      },
    };
    costChartInstance.setOption(option);
  };
  // æ›´æ–°èƒ½è€—类型成本占比图
  const updateTypeChart = () => {
    const data = tableData.value;
    const typeCosts = {};
    data.forEach(item => {
      if (!typeCosts[item.energyType]) {
        typeCosts[item.energyType] = 0;
      }
      typeCosts[item.energyType] += parseFloat(item.cost);
    });
    const chartData = Object.entries(typeCosts).map(([name, value]) => ({
      name,
      value: value.toFixed(2),
    }));
    const option = {
      tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b}: Â¥{c} ({d}%)",
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      legend: {
        orient: "horizontal",
        bottom: 0,
        textStyle: { color: "#606266" },
      },
      series: [
        {
          name: "能耗类型成本",
          type: "pie",
          radius: ["40%", "70%"],
          center: ["50%", "40%"],
          avoidLabelOverlap: false,
          itemStyle: {
            borderRadius: 4,
            borderColor: "#fff",
            borderWidth: 2,
          },
          label: {
            show: false,
            position: "center",
          },
          emphasis: {
            label: {
              show: true,
              fontSize: "18",
              fontWeight: "bold",
              color: "#303133",
            },
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.3)",
            },
          },
          labelLine: {
            show: false,
          },
          data: chartData,
        },
      ],
      color: ["#409EFF", "#67C23A", "#E6A23C"],
    };
    typeChartInstance.setOption(option);
  };
  // æ›´æ–°èƒ½è€—用途成本占比图
  const updatePurposeChart = () => {
    const data = tableData.value;
    const purposeCosts = {
      ç”Ÿäº§: 0,
      åŠžå…¬: 0,
    };
    data.forEach(item => {
      if (purposeCosts.hasOwnProperty(item.energyPurpose)) {
        purposeCosts[item.energyPurpose] += parseFloat(item.cost);
      }
    });
    const chartData = Object.entries(purposeCosts).map(([name, value]) => ({
      name,
      value: value.toFixed(2),
    }));
    const option = {
      tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b}: Â¥{c} ({d}%)",
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      legend: {
        orient: "horizontal",
        bottom: 0,
        textStyle: { color: "#606266" },
      },
      series: [
        {
          name: "能耗用途成本",
          type: "pie",
          radius: "60%",
          center: ["50%", "40%"],
          data: chartData,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.3)",
            },
          },
          label: {
            show: true,
            formatter: "{b}: {d}%",
            color: "#606266",
          },
          labelLine: {
            show: true,
            lineStyle: { color: "#dcdfe6" },
          },
        },
      ],
      color: ["#409EFF", "#67C23A"],
    };
    purposeChartInstance.setOption(option);
  };
  // æ›´æ–°èƒ½è€—单价对比图
  const updatePriceChart = () => {
    const data = tableData.value;
    const priceData = {};
    data.forEach(item => {
      if (!priceData[item.energyType]) {
        priceData[item.energyType] = {
          ç”Ÿäº§: 0,
          åŠžå…¬: 0,
        };
      }
      if (priceData[item.energyType].hasOwnProperty(item.energyPurpose)) {
        priceData[item.energyType][item.energyPurpose] = parseFloat(item.price);
      }
    });
    const energyTypes = Object.keys(priceData);
    const productionPrices = energyTypes.map(type => priceData[type].生产);
    const officePrices = energyTypes.map(type => priceData[type].办公);
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      legend: {
        data: ["生产能耗单价", "办公能耗单价"],
        top: 0,
        right: 10,
        textStyle: { color: "#606266" },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "10%",
        top: "15%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: energyTypes,
        axisLabel: { color: "#606266" },
        axisLine: { lineStyle: { color: "#ebeef5" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "单价(元)",
        nameTextStyle: { color: "#606266" },
        axisLabel: { color: "#606266" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "#f0f2f5" } },
      },
      series: [
        {
          name: "生产能耗单价",
          type: "bar",
          data: productionPrices,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#409EFF" },
              { offset: 1, color: "#66b1ff" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
        },
        {
          name: "办公能耗单价",
          type: "bar",
          data: officePrices,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#67C23A" },
              { offset: 1, color: "#85ce61" },
            ]),
            borderRadius: [4, 4, 0, 0],
          },
        },
      ],
    };
    priceChartInstance.setOption(option);
  };
  // ç»Ÿè®¡ç»´åº¦åˆ‡æ¢
  const handleTypeChange = () => {
    // é‡ç½®æ—¶é—´èŒƒå›´
    if (statisticsType.value === "day") {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      searchForm.dateRange = [
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      searchForm.monthRange = [
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
    }
    page.current = 1;
    handleQuery();
  };
  // æŸ¥è¯¢
  const handleQuery = () => {
    tableLoading.value = true;
    // æž„造请求参数
    const params = {
      type: statisticsType.value,
      energyType: searchForm.energyType || undefined,
      energyPurpose: searchForm.energyPurpose || undefined,
    };
    if (statisticsType.value === "day") {
      if (searchForm.dateRange && searchForm.dateRange.length === 2) {
        params.startDate = searchForm.dateRange[0];
        params.endDate = searchForm.dateRange[1];
      }
    } else {
      if (searchForm.monthRange && searchForm.monthRange.length === 2) {
        params.startDate = searchForm.monthRange[0] + "-01";
        params.endDate = searchForm.monthRange[1] + "-01";
      }
    }
    // è°ƒç”¨æŽ¥å£èŽ·å–æ•°æ®
    energyCostStatistics(params)
      .then(res => {
        if (res.code === 200) {
          tableData.value = res.data.records || [];
          page.total = res.data.total || 0;
          // æ›´æ–°ç»Ÿè®¡æ¦‚览数据
          if (res.data.overview) {
            overview.totalCost = res.data.overview.totalCost || "0.00";
            overview.productionCost = res.data.overview.productionCost || "0.00";
            overview.officeCost = res.data.overview.officeCost || "0.00";
            overview.avgCost = res.data.overview.avgCost || "0.00";
          }
        } else {
          ElMessage.error(res.message || "获取数据失败");
          tableData.value = [];
          page.total = 0;
        }
      })
      .catch(err => {
        console.error("获取数据异常:", err);
        // ç”Ÿæˆå‡æ•°æ®
        generateMockData();
      })
      .finally(() => {
        tableLoading.value = false;
        updateCharts();
      });
  };
  // ç”Ÿæˆå‡æ•°æ®
  const generateMockData = () => {
    if (statisticsType.value === "day") {
      // ç”Ÿæˆæœ€è¿‘7天的假数据
      const mockData = [];
      const today = new Date();
      for (let i = 6; i >= 0; i--) {
        const date = new Date(today);
        date.setDate(date.getDate() - i);
        const dateStr = date.toISOString().split("T")[0];
        // ç”Ÿäº§èƒ½è€—数据
        mockData.push({
          timePeriod: dateStr,
          energyType: "电",
          energyPurpose: "生产",
          consumption: (Math.random() * 1000 + 500).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 850 + 425).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "æ°´",
          energyPurpose: "生产",
          consumption: (Math.random() * 500 + 200).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 1750 + 700).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "气",
          energyPurpose: "生产",
          consumption: (Math.random() * 300 + 100).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 840 + 280).toFixed(2),
        });
        // åŠžå…¬èƒ½è€—æ•°æ®
        mockData.push({
          timePeriod: dateStr,
          energyType: "电",
          energyPurpose: "办公",
          consumption: (Math.random() * 200 + 100).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 170 + 85).toFixed(2),
        });
        mockData.push({
          timePeriod: dateStr,
          energyType: "æ°´",
          energyPurpose: "办公",
          consumption: (Math.random() * 50 + 20).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 175 + 70).toFixed(2),
        });
      }
      tableData.value = mockData;
      page.total = mockData.length;
    } else {
      // ç”Ÿæˆæœ€è¿‘3个月的假数据
      const mockData = [];
      const today = new Date();
      for (let i = 2; i >= 0; i--) {
        const date = new Date(today);
        date.setMonth(date.getMonth() - i);
        const monthStr = date.toISOString().slice(0, 7);
        // ç”Ÿäº§èƒ½è€—数据
        mockData.push({
          timePeriod: monthStr,
          energyType: "电",
          energyPurpose: "生产",
          consumption: (Math.random() * 30000 + 15000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 25500 + 12750).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "æ°´",
          energyPurpose: "生产",
          consumption: (Math.random() * 15000 + 6000).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 52500 + 21000).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "气",
          energyPurpose: "生产",
          consumption: (Math.random() * 9000 + 3000).toFixed(2),
          unit: "m³",
          price: "2.80",
          cost: (Math.random() * 25200 + 8400).toFixed(2),
        });
        // åŠžå…¬èƒ½è€—æ•°æ®
        mockData.push({
          timePeriod: monthStr,
          energyType: "电",
          energyPurpose: "办公",
          consumption: (Math.random() * 6000 + 3000).toFixed(2),
          unit: "kWh",
          price: "0.85",
          cost: (Math.random() * 5100 + 2550).toFixed(2),
        });
        mockData.push({
          timePeriod: monthStr,
          energyType: "æ°´",
          energyPurpose: "办公",
          consumption: (Math.random() * 1500 + 600).toFixed(2),
          unit: "m³",
          price: "3.50",
          cost: (Math.random() * 5250 + 2100).toFixed(2),
        });
      }
      tableData.value = mockData;
      page.total = mockData.length;
    }
    // æ›´æ–°ç»Ÿè®¡æ¦‚览数据
    calculateOverview();
  };
  // è®¡ç®—统计概览数据
  const calculateOverview = () => {
    let totalCost = 0;
    let productionCost = 0;
    let officeCost = 0;
    tableData.value.forEach(item => {
      const cost = parseFloat(item.cost);
      totalCost += cost;
      if (item.energyPurpose === "生产") {
        productionCost += cost;
      } else if (item.energyPurpose === "办公") {
        officeCost += cost;
      }
    });
    overview.totalCost = totalCost.toFixed(2);
    overview.productionCost = productionCost.toFixed(2);
    overview.officeCost = officeCost.toFixed(2);
    overview.avgCost = (totalCost / tableData.value.length).toFixed(2);
  };
  // æ›´æ–°æ‰€æœ‰å›¾è¡¨
  const updateCharts = () => {
    nextTick(() => {
      if (costChartInstance) updateCostChart();
      if (typeChartInstance) updateTypeChart();
      if (purposeChartInstance) updatePurposeChart();
      if (priceChartInstance) updatePriceChart();
    });
  };
  // é‡ç½®
  const handleReset = () => {
    searchForm.energyType = "";
    searchForm.energyPurpose = "";
    if (statisticsType.value === "day") {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      searchForm.dateRange = [
        start.toISOString().split("T")[0],
        end.toISOString().split("T")[0],
      ];
    } else {
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      searchForm.monthRange = [
        start.toISOString().slice(0, 7),
        end.toISOString().slice(0, 7),
      ];
    }
    page.current = 1;
    handleQuery();
  };
  // å¯¼å‡º
  const handleExport = () => {
    ElMessage.success("报表导出成功");
  };
  // åˆ†é¡µå¤§å°å˜åŒ–
  const handleSizeChange = val => {
    page.size = val;
  };
  // é¡µç å˜åŒ–
  const handleCurrentChange = val => {
    page.current = val;
  };
  // çª—口大小变化时重新渲染图表
  const handleResize = () => {
    costChartInstance && costChartInstance.resize();
    typeChartInstance && typeChartInstance.resize();
    purposeChartInstance && purposeChartInstance.resize();
    priceChartInstance && priceChartInstance.resize();
  };
  onMounted(() => {
    handleQuery();
    initCharts();
    window.addEventListener("resize", handleResize);
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 20px;
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding: 15px;
    background-color: #f5f7fa;
    border-radius: 8px;
  }
  .statistics-overview {
    margin-bottom: 30px;
  }
  .charts-container {
    margin-bottom: 30px;
  }
  .table-section {
    margin-bottom: 20px;
  }
  .section-header {
    display: flex;
    align-items: center;
    font-size: 18px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 15px;
    padding-left: 10px;
    border-left: 3px solid #409eff;
    .header-icon {
      margin-right: 8px;
      color: #409eff;
    }
  }
  .overview-card {
    display: flex;
    align-items: center;
    padding: 20px;
    border-radius: 4px;
    background: #fff;
    border: 1px solid #ebeef5;
    &.blue-card {
      background-color: #ecf5ff;
    }
    &.green-card {
      background-color: #f0f9eb;
    }
    &.purple-card {
      background-color: #f3f0ff;
    }
    &.gray-card {
      background-color: #f5f7fa;
    }
    .overview-icon {
      width: 40px;
      height: 40px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
      margin-right: 15px;
      &.blue-icon {
        background-color: #409eff;
        color: #fff;
      }
      &.green-icon {
        background-color: #67c23a;
        color: #fff;
      }
      &.purple-icon {
        background-color: #909399;
        color: #fff;
      }
      &.gray-icon {
        background-color: #909399;
        color: #fff;
      }
      .el-icon {
        font-size: 20px;
      }
    }
    .overview-info {
      flex: 1;
      .overview-label {
        font-size: 14px;
        color: #606266;
        margin-bottom: 5px;
      }
      .overview-value {
        font-size: 20px;
        font-weight: bold;
        color: #303133;
        .unit {
          font-size: 12px;
          font-weight: normal;
          color: #909399;
        }
      }
    }
  }
  .chart-card {
    background: #fff;
    border-radius: 4px;
    border: 1px solid #ebeef5;
    padding: 20px;
    .chart-title {
      font-size: 14px;
      font-weight: bold;
      color: #303133;
      margin-bottom: 15px;
      padding-bottom: 10px;
      border-bottom: 1px solid #ebeef5;
    }
    .chart-content {
      height: 300px;
    }
  }
  .consumption-value {
    font-weight: bold;
    color: #409eff;
  }
  .consumption-unit {
    font-size: 12px;
    color: #909399;
    margin-left: 2px;
  }
  .price-value {
    font-weight: bold;
    color: #67c23a;
  }
  .cost-value {
    font-weight: bold;
    color: #f56c6c;
  }
  .pagination-container {
    display: flex;
    justify-content: flex-end;
  }
</style>
src/views/energyManagement/energyConsumptionStatistical/index.vue
@@ -267,15 +267,28 @@
    ArrowDown,
  } from "@element-plus/icons-vue";
  import * as echarts from "echarts";
  import { energyConsumptionDetailStatistics } from "@/api/energyManagement/energyType";
  // ç»Ÿè®¡ç»´åº¦ï¼šday-按日,month-按月,year-按年
  const statisticsType = ref("day");
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    energyType: "",
    dateRange: [],
    monthRange: [],
    energyType: "全部",
    dateRange: (() => {
      // é»˜è®¤æœ€è¿‘7天
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 6);
      return [start.toISOString().split("T")[0], end.toISOString().split("T")[0]];
    })(),
    monthRange: (() => {
      // é»˜è®¤æœ€è¿‘3个月
      const end = new Date();
      const start = new Date();
      start.setMonth(start.getMonth() - 2);
      return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
    })(),
    year: new Date().getFullYear(),
  });
@@ -651,13 +664,73 @@
  // æŸ¥è¯¢
  const handleQuery = () => {
    tableLoading.value = true;
    setTimeout(() => {
      const data = generateMockData();
      tableData.value = data;
      page.total = data.length;
      tableLoading.value = false;
      updateCharts();
    }, 300);
    const params = {
      type: "",
    };
    // æž„造请求参数
    if (searchForm.energyType != "全部") {
      params.type = searchForm.energyType;
    }
    if (statisticsType.value === "day") {
      if (searchForm.dateRange && searchForm.dateRange.length === 2) {
        params.startDate = searchForm.dateRange[0];
        params.endDate = searchForm.dateRange[1];
        // è®¡ç®—天数
        const start = new Date(searchForm.dateRange[0]);
        const end = new Date(searchForm.dateRange[1]);
        params.days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
      }
    } else if (statisticsType.value === "month") {
      if (searchForm.monthRange && searchForm.monthRange.length === 2) {
        params.startDate = searchForm.monthRange[0] + "-01";
        params.endDate = searchForm.monthRange[1] + "-01";
        // è®¡ç®—月数
        const start = new Date(searchForm.monthRange[0] + "-01");
        const end = new Date(searchForm.monthRange[1] + "-01");
        params.days =
          (end.getFullYear() - start.getFullYear()) * 12 +
          (end.getMonth() - start.getMonth()) +
          1;
      }
    } else if (statisticsType.value === "year") {
      params.startDate = searchForm.year + "-01-01";
      params.endDate = searchForm.year + "-12-31";
      params.days = 365;
    }
    // è°ƒç”¨æŽ¥å£èŽ·å–æ•°æ®
    energyConsumptionDetailStatistics(params)
      .then(res => {
        if (res.code === 200) {
          const data = res.data;
          // æ›´æ–°ç»Ÿè®¡æ¦‚览数据
          overview.totalConsumption = data.totalEnergyConsumption || "0";
          overview.totalAmount = data.totalEnergyCost || "0";
          overview.avgConsumption = data.averageConsumption || "0";
          overview.compareRate = data.changeVite || 0;
          // å¤„理表格数据
          tableData.value = data.energyCostDtos || [];
          page.total = tableData.value.length || 0;
        } else {
          ElMessage.error(res.message || "获取数据失败");
          tableData.value = [];
          page.total = 0;
        }
      })
      .catch(err => {
        console.error("获取数据异常:", err);
        ElMessage.error("系统异常,获取数据失败");
        tableData.value = [];
        page.total = 0;
      })
      .finally(() => {
        tableLoading.value = false;
        updateCharts();
      });
  };
  // æ›´æ–°æ‰€æœ‰å›¾è¡¨
@@ -672,10 +745,28 @@
  // é‡ç½®
  const handleReset = () => {
    searchForm.energyType = "";
    searchForm.dateRange = [];
    searchForm.monthRange = [];
    searchForm.energyType = "全部";
    // é‡ç½®ä¸ºé»˜è®¤æ—¶é—´èŒƒå›´
    const end = new Date();
    const start = new Date();
    // é»˜è®¤æœ€è¿‘7天
    start.setDate(start.getDate() - 6);
    searchForm.dateRange = [
      start.toISOString().split("T")[0],
      end.toISOString().split("T")[0],
    ];
    // é»˜è®¤æœ€è¿‘3个月
    start.setMonth(start.getMonth() - 2);
    searchForm.monthRange = [
      start.toISOString().slice(0, 7),
      end.toISOString().slice(0, 7),
    ];
    // é»˜è®¤å½“前年份
    searchForm.year = new Date().getFullYear();
    page.current = 1;
    handleQuery();
  };
src/views/reportAnalysis/unitEnergyConsumption/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,405 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="年份:">
          <el-select v-model="searchForm.year"
                     placeholder="请选择年份"
                     style="width: 120px;"
                     @change="handleQuery">
            <el-option v-for="year in years"
                       :key="year"
                       :label="year + 'å¹´'"
                       :value="year" />
          </el-select>
        </el-form-item>
        <el-form-item label="能耗类型:">
          <el-select v-model="searchForm.energyType"
                     placeholder="全部"
                     clearable
                     style="width: 140px;"
                     @change="handleQuery">
            <el-option label="全部"
                       value="全部" />
            <el-option label="æ°´"
                       value="æ°´" />
            <el-option label="电"
                       value="电" />
            <el-option label="蒸汽"
                       value="蒸汽" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">查询</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="success"
                   @click="handleExport">导出报表</el-button>
      </div>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="chart-section">
      <h2 class="section-header">
        <el-icon class="header-icon">
          <TrendCharts />
        </el-icon>
        èƒ½è€—单耗趋势
      </h2>
      <div class="chart-card">
        <div ref="consumptionChart"
             class="chart-content"></div>
      </div>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table-section">
      <h2 class="section-header">
        <el-icon class="header-icon">
          <List />
        </el-icon>
        èƒ½è€—单耗数据
      </h2>
      <el-table :data="tableData"
                v-loading="tableLoading"
                border>
        <el-table-column prop="energyType"
                         label="能耗"
                         width="100"
                         align="center">
          <template #default="scope">
            <el-tag :type="getEnergyTypeType(scope.row.energyType)">
              {{ scope.row.energyType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="unit"
                         label="单位"
                         width="120"
                         align="center" />
        <el-table-column label="月度数据">
          <el-table-column prop="monthlyUnitConsumption"
                           label="月度累计单耗"
                           align="right">
            <template #default="scope">
              <span class="data-value">{{ scope.row.monthlyUnitConsumption }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="monthlyConsumption"
                           label="月度累计用量/月度累计产量"
                           align="right">
            <template #default="scope">
              <span class="data-value">{{ scope.row.monthlyConsumption }}/{{ scope.row.monthlyProduction }}</span>
            </template>
          </el-table-column>
        </el-table-column>
        <el-table-column label="年度数据">
          <el-table-column prop="annualUnitConsumption"
                           label="年度累计单耗"
                           align="right">
            <template #default="scope">
              <span class="data-value">{{ scope.row.annualUnitConsumption }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="annualConsumption"
                           label="年度累计用量/年度累计产量"
                           align="right">
            <template #default="scope">
              <span class="data-value">{{ scope.row.annualConsumption }}/{{ scope.row.annualProduction }}</span>
            </template>
          </el-table-column>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script setup>
  import { ref, reactive, onMounted, nextTick } from "vue";
  import { ElMessage } from "element-plus";
  import { TrendCharts, List } from "@element-plus/icons-vue";
  import * as echarts from "echarts";
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    year: new Date().getFullYear(),
    energyType: "",
  });
  // ç”Ÿæˆå¹´ä»½é€‰é¡¹
  const years = [];
  const currentYear = new Date().getFullYear();
  for (let i = currentYear - 5; i <= currentYear; i++) {
    years.push(i);
  }
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  const tableLoading = ref(false);
  // å›¾è¡¨å¼•用
  const consumptionChart = ref(null);
  let consumptionChartInstance = null;
  // èŽ·å–èƒ½è€—ç±»åž‹æ ‡ç­¾ç±»åž‹
  const getEnergyTypeType = type => {
    const typeMap = {
      æ°´: "primary",
      ç”µ: "warning",
      è’¸æ±½: "success",
    };
    return typeMap[type] || "info";
  };
  // åˆå§‹åŒ–图表
  const initChart = () => {
    nextTick(() => {
      if (consumptionChart.value) {
        consumptionChartInstance = echarts.init(consumptionChart.value);
        updateChart();
      }
    });
  };
  // æ›´æ–°å›¾è¡¨
  const updateChart = () => {
    const data = tableData.value;
    const months = [
      "1月",
      "2月",
      "3月",
      "4月",
      "5月",
      "6月",
      "7月",
      "8月",
      "9月",
      "10月",
      "11月",
      "12月",
    ];
    // å‡†å¤‡å›¾è¡¨æ•°æ®
    const series = [];
    const energyTypes = ["æ°´", "电", "蒸汽"];
    energyTypes.forEach(type => {
      const typeData = data.find(item => item.energyType === type);
      if (typeData && typeData.monthlyData) {
        series.push({
          name: type,
          type: "line",
          data: typeData.monthlyData.map(item => item.unitConsumption),
          smooth: true,
          symbol: "circle",
          symbolSize: 8,
          lineStyle: {
            width: 3,
          },
          itemStyle: {
            color:
              getEnergyTypeType(type) === "primary"
                ? "#409EFF"
                : getEnergyTypeType(type) === "warning"
                ? "#E6A23C"
                : "#67C23A",
          },
        });
      }
    });
    const option = {
      tooltip: {
        trigger: "axis",
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      legend: {
        data: energyTypes,
        top: 0,
        right: 10,
        textStyle: { color: "#606266" },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "10%",
        top: "15%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: months,
        axisLabel: { color: "#606266" },
        axisLine: { lineStyle: { color: "#ebeef5" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "单耗",
        nameTextStyle: { color: "#606266" },
        axisLabel: { color: "#606266" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "#f0f2f5" } },
      },
      series: series,
    };
    consumptionChartInstance.setOption(option);
  };
  // æŸ¥è¯¢
  const handleQuery = () => {
    tableLoading.value = true;
    // æ¨¡æ‹ŸæŽ¥å£è°ƒç”¨
    setTimeout(() => {
      generateMockData();
      tableLoading.value = false;
      updateChart();
    }, 500);
  };
  // é‡ç½®
  const handleReset = () => {
    searchForm.year = new Date().getFullYear();
    searchForm.energyType = "";
    handleQuery();
  };
  // å¯¼å‡º
  const handleExport = () => {
    ElMessage.success("报表导出成功");
  };
  // ç”Ÿæˆå‡æ•°æ®
  const generateMockData = () => {
    const energyTypes = [
      {
        energyType: "æ°´",
        unit: "吨/立方米",
        monthlyUnitConsumption: (Math.random() * 0.5 + 0.8).toFixed(4),
        monthlyConsumption: Math.floor(Math.random() * 5000 + 10000),
        monthlyProduction: Math.floor(Math.random() * 10000 + 20000),
        annualUnitConsumption: (Math.random() * 0.3 + 0.9).toFixed(4),
        annualConsumption: Math.floor(Math.random() * 60000 + 120000),
        annualProduction: Math.floor(Math.random() * 120000 + 240000),
        monthlyData: generateMonthlyData(0.8, 1.3),
      },
      {
        energyType: "电",
        unit: "度/立方米",
        monthlyUnitConsumption: (Math.random() * 2 + 5).toFixed(4),
        monthlyConsumption: Math.floor(Math.random() * 50000 + 100000),
        monthlyProduction: Math.floor(Math.random() * 10000 + 20000),
        annualUnitConsumption: (Math.random() * 1.5 + 5.5).toFixed(4),
        annualConsumption: Math.floor(Math.random() * 600000 + 1200000),
        annualProduction: Math.floor(Math.random() * 120000 + 240000),
        monthlyData: generateMonthlyData(5, 7),
      },
      {
        energyType: "蒸汽",
        unit: "吨/立方米",
        monthlyUnitConsumption: (Math.random() * 0.3 + 0.5).toFixed(4),
        monthlyConsumption: Math.floor(Math.random() * 3000 + 6000),
        monthlyProduction: Math.floor(Math.random() * 10000 + 20000),
        annualUnitConsumption: (Math.random() * 0.2 + 0.55).toFixed(4),
        annualConsumption: Math.floor(Math.random() * 36000 + 72000),
        annualProduction: Math.floor(Math.random() * 120000 + 240000),
        monthlyData: generateMonthlyData(0.5, 0.8),
      },
    ];
    if (searchForm.energyType && searchForm.energyType !== "全部") {
      tableData.value = energyTypes.filter(
        item => item.energyType === searchForm.energyType
      );
    } else {
      tableData.value = energyTypes;
    }
  };
  // ç”Ÿæˆæœˆåº¦æ•°æ®
  const generateMonthlyData = (min, max) => {
    const data = [];
    for (let i = 1; i <= 12; i++) {
      data.push({
        month: i,
        unitConsumption: (Math.random() * (max - min) + min).toFixed(4),
      });
    }
    return data;
  };
  // çª—口大小变化时重新渲染图表
  const handleResize = () => {
    consumptionChartInstance && consumptionChartInstance.resize();
  };
  onMounted(() => {
    handleQuery();
    initChart();
    window.addEventListener("resize", handleResize);
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 20px;
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding: 15px;
    background-color: #f5f7fa;
    border-radius: 8px;
  }
  .chart-section {
    margin-bottom: 30px;
  }
  .table-section {
    margin-bottom: 20px;
  }
  .section-header {
    display: flex;
    align-items: center;
    font-size: 18px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 15px;
    padding-left: 10px;
    border-left: 3px solid #409eff;
    .header-icon {
      margin-right: 8px;
      color: #409eff;
    }
  }
  .chart-card {
    background: #fff;
    border-radius: 4px;
    border: 1px solid #ebeef5;
    padding: 20px;
    .chart-content {
      height: 400px;
    }
  }
  .data-value {
    font-weight: bold;
    color: #409eff;
  }
</style>