张诺
19 小时以前 7619d415522ab3dc3299d6a2a9f5c9964a692d3f
添加生产管理接口及优化表格字段
已修改12个文件
已删除1个文件
已添加5个文件
1477 ■■■■■ 文件已修改
src/api/production/index.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/publicApi/index.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/ETable.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/EtableModify.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/production.js 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/archiveManagement/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/archiveManagement/mould/archiveDialog.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/components/ProductionDialog.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/ProductionDetailsTable.vue 358 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/ProductionDetailsTableExample.vue 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/ProductionDialog.vue 297 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/useCoalData.js 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/useDialog.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/useTableData.js 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/index.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/production/index.js
@@ -49,3 +49,22 @@
    })
}
// /productionMaster/deleteProductionInventory
// åˆ é™¤ä¸»è¡¨
export function deleteProductionInventory(data) {
    return request({
        url: '/productionMaster/deleteProductionInventory',
        method: 'delete',
        data: data
    })
}
// /productionMaster/delPM
// åˆ é™¤ä¸»è¡¨
export function delPM(data) {
    return request({
        url: '/productionMaster/delPM',
        method: 'delete',
        data: data
    })
}
src/api/publicApi/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
// æ–‡æ¡£ç®¡ç†
import request from '@/utils/request'
// /system/user/listAll
// æŸ¥è¯¢æ‰€æœ‰ç”¨æˆ·åˆ—表
export function userListAll() {
  return request({
    url: '/system/user/listAll',
    method: 'get'
  })
}
src/components/FileUpload/index.vue
@@ -131,7 +131,13 @@
  number.value = 0;
}
const editInit = (val) => {
  console.log("editInit", val);
  fileList.value = [];
  val.storageBlobDTO.forEach(element => {
    console.log("编辑初始化", element);
    fileList.value.push(element);
    uploadedSuccessfully();
  });
  // uploadList.value.push
};
defineExpose({
  init,
@@ -179,6 +185,7 @@
    })
    .then((response) => {
      if (response.data.code === 200) {
        console.log("上传成功", response.data);
        handleUploadSuccess(response.data, param.file);
        // æ›´æ–°çˆ¶ç»„ä»¶
        emit("update:modelValue", fileList.value);
src/components/Pagination/index.vue
@@ -59,7 +59,7 @@
  }
})
const emit = defineEmits()
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const currentPage = computed({
  get() {
    return props.page
src/components/Table/ETable.vue
@@ -18,9 +18,13 @@
    style="width: 100%"
  >
    <el-table-column v-if="showSelection" type="selection" width="55" align="center" />
    <el-table-column v-if="showIndex" label="序号" type="index" width="60" align="center" />
    <el-table-column v-if="showIndex" label="序号" width="60" align="center" fixed="left">
      <template #default="scope">
        {{ getRowIndex(scope.$index) }}
      </template>
    </el-table-column>
    <template v-for="col in columns" :key="col.prop">
      <el-table-column v-bind="col" :show-overflow-tooltip="shouldShowTooltip(col, tableData)"
      <el-table-column v-bind="col"
        :formatter="col.formatter || defaultFormatter" align="center">
        <template v-if="col.slot" #default="scope">
          <slot :name="col.prop" :row="scope.row" :column="scope.column" :index="scope.$index"></slot>
@@ -139,24 +143,22 @@
  rowKey: {
    type: String,
    default: 'id'
  },
  showOverflowTooltip: {
  },  showOverflowTooltip: {
    type: Boolean,
    default: true
  },
  // å½“前页码
  currentPage: {
    type: Number,
    default: 1
  },
  // æ¯é¡µå¤§å°
  pageSize: {
    type: Number,
    default: 10
  }
})
const tableRef = ref(null)
// æ£€æŸ¥åˆ—是否需要显示tooltip
const shouldShowTooltip = (col, data) => {
  // å¦‚果列配置中明确设置了showOverflowTooltip,使用该设置
  if (col.hasOwnProperty('showOverflowTooltip')) {
    return col.showOverflowTooltip;
  }
  // å¦‚果没有prop,直接返回false
  if (!col.prop) return false;
  // æ£€æŸ¥è¯¥åˆ—在所有数据中是否有非空值,默认显示tooltip
  return data.some(row => row[col.prop] != null && row[col.prop] !== '');
};
// é»˜è®¤çš„æ ¼å¼åŒ–函数
const defaultFormatter = (row, column, cellValue) => {
@@ -188,6 +190,11 @@
const handleExport = (row) => {
  emit('export', row)
}
// è®¡ç®—分页序号
const getRowIndex = (index) => {
  return (props.currentPage - 1) * props.pageSize + index + 1;
};
// æ­£ç¡®çš„ toggleRowSelection æ–¹æ³•:针对单行
const toggleRowSelection = (row, selected) => {
@@ -270,6 +277,9 @@
</script>
  
  <style scoped>
  :deep(.el-tooltip) {
    justify-content: center !important;
  }
.el-table {
    margin: 20px 0 !important;
  }
src/components/Table/EtableModify.vue
@@ -32,7 +32,7 @@
    <template v-for="col in columns" :key="col.prop">
      <el-table-column
        v-bind="col"
        :show-overflow-tooltip="shouldShowTooltip(col, tableData)"
        :show-overflow-tooltip="false"
        align="center"
      >
        <template #default="scope">
src/utils/production.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
/**
 * ç”Ÿäº§æ¨¡å—工具函数
 * æä¾›æ•°æ®å¤„理、验证、转换等通用方法
 */
/**
 * è§£æžç…¤ç§æ•°ç»„字符串
 * @param {string|array} coalString - ç…¤ç§å­—符串或数组
 * @returns {array} è§£æžåŽçš„煤种数组
 */
export function parseCoalArray(coalString) {
  if (!coalString) return [];
  if (Array.isArray(coalString)) return coalString;
  return String(coalString)
    .replace(/^\[|\]$/g, '')
    .split(',')
    .map(item => item.trim())
    .filter(Boolean);
}
/**
 * æ ¹æ®ID匹配列表中的项目
 * @param {array} list - æ•°æ®åˆ—表
 * @param {string|number} value - è¦åŒ¹é…çš„值
 * @param {string} keyField - é”®å­—段名,默认为'id'
 * @param {string} valueField - å€¼å­—段名,默认为'fieldName'
 * @returns {string} åŒ¹é…åˆ°çš„值或原值
 */
export function matchItemById(list, value, keyField = 'id', valueField = 'fieldName') {
  if (!Array.isArray(list) || !value) return value;
  const found = list.find(item => item[keyField] == value);
  return found ? found[valueField] : value;
}
/**
 * è®¡ç®—总成本
 * @param {object} row - æ•°æ®è¡Œ
 * @param {array} fields - éœ€è¦è®¡ç®—的字段数组
 * @returns {string} æ ¼å¼åŒ–后的总成本
 */
export function calculateTotalCost(row, fields = ['laborCost', 'energyConsumptionCost', 'equipmentDepreciation', 'purchasePrice']) {
  const total = fields.reduce((sum, field) => {
    return sum + (parseFloat(row[field]) || 0);
  }, 0);
  return total.toFixed(2);
}
/**
 * éªŒè¯è¡¨å•数据
 * @param {array} data - è¡¨å•数据数组
 * @param {array} requiredFields - å¿…填字段
 * @returns {object} éªŒè¯ç»“æžœ { isValid, message }
 */
export function validateFormData(data, requiredFields) {
  if (!data || data.length === 0) {
    return { isValid: false, message: '请添加数据' };
  }
  for (let i = 0; i < data.length; i++) {
    const item = data[i];
    for (const field of requiredFields) {
      if (item[field] === '' || item[field] === null || item[field] === undefined) {
        return {
          isValid: false,
          message: `第${i + 1}行的${field}字段不能为空`
        };
      }
    }
  }
  return { isValid: true, message: '验证通过' };
}
/**
 * æ•°å€¼éªŒè¯
 * @param {any} value - è¦éªŒè¯çš„值
 * @param {number} min - æœ€å°å€¼
 * @param {number} max - æœ€å¤§å€¼
 * @returns {object} éªŒè¯ç»“æžœ { isValid, message, value }
 */
export function validateNumber(value, min = 0, max = Infinity) {
  const numValue = Number(value);
  if (isNaN(numValue)) {
    return { isValid: false, message: '请输入有效数字', value: 0 };
  }
  if (numValue < min) {
    return { isValid: false, message: `数值不能小于${min}`, value: min };
  }
  if (numValue > max) {
    return { isValid: false, message: `数值不能大于${max}`, value: max };
  }
  return { isValid: true, message: '验证通过', value: numValue };
}
/**
 * æ·±æ‹·è´å¯¹è±¡
 * @param {any} obj - è¦æ‹·è´çš„对象
 * @returns {any} æ‹·è´åŽçš„对象
 */
export function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj.getTime());
  if (obj instanceof Array) return obj.map(item => deepClone(item));
  if (typeof obj === 'object') {
    const clonedObj = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        clonedObj[key] = deepClone(obj[key]);
      }
    }
    return clonedObj;
  }
}
/**
 * ç”Ÿæˆé»˜è®¤çš„生产明细行数据
 * @param {object} userData - ç”¨æˆ·æ•°æ®
 * @param {object} extraData - é¢å¤–数据
 * @returns {object} é»˜è®¤è¡Œæ•°æ®
 */
export function createDefaultProductionRow(userData = {}, extraData = {}) {
  return {
    coal: "",
    productionQuantity: "",
    laborCost: "",
    energyConsumptionCost: "",
    equipmentDepreciation: "",
    purchasePrice: "",
    totalCost: "0.00",
    producer: userData?.user?.nickName || "",
    ...extraData
  };
}
src/views/archiveManagement/index.vue
@@ -191,8 +191,6 @@
  return data.name?.toLowerCase().includes(value.toLowerCase());
};
const submitForm = async (res) => {
  console.log("提交表单回调:", res);
  if (res && res.code === 200) {
    ElMessage.success("操作成功");
    // åˆ·æ–°åˆ—表数据
@@ -202,11 +200,9 @@
  }
}
const centerDialogVisible = (val) => {
  console.log(val);
};
// å¤„理节点点击
const handleNodeClick = async (data) => {
  console.log("点击节点:", data);
  // åˆ‡æ¢èŠ‚ç‚¹æ—¶é‡ç½®åˆ°ç¬¬ä¸€é¡µ
  queryParams.current = 1;
  queryParams.treeId = data.id;
@@ -222,7 +218,6 @@
};
// å¤„理分页变化
const handlePageChange = ({ page }) => {
  console.log("分页变化:", { page });
  queryParams.current = page;
  // pageSize å›ºå®šä¸º20,不再从参数中获取
  getArchiveListData();
@@ -231,12 +226,6 @@
const getArchiveListData = async () => {
  try {
    loading.value = true;
    console.log("获取归档列表数据", {
      treeId: queryParams.treeId,
      current: queryParams.current,
      pageSize: queryParams.pageSize,
    });
    let res = await getArchiveList({
      treeId: queryParams.treeId,
      current: queryParams.current,
@@ -258,14 +247,7 @@
    }
    // pageSize å›ºå®šä¸º20,不从后端获取
    console.log("数据更新完成:", {
      total: total.value,
      current: queryParams.current,
      pageSize: queryParams.pageSize,
      records: tableData.value.length,
    });
  } catch (error) {
    console.error("获取归档列表失败:", error);
    ElMessage.error("获取数据失败");
    tableData.value = [];
    total.value = 0;
@@ -280,7 +262,6 @@
  }
  try {
    const ids = selectedRows.map((row) => row.id);
    console.log(ids)
    const { code, msg } = await delArchive(ids);
    if (code !== 200) {
      ElMessage.warning("删除失败: " + msg);
@@ -290,7 +271,6 @@
      await getArchiveListData();
    }
  } catch (error) {
    console.error("删除归档失败:", error);
    ElMessage.error("删除归档失败");
  }
};
@@ -329,11 +309,9 @@
  comeTreeData.isEdit = false;
  const newValue = newName.value.trim();
  if (comeTreeData.name === newValue) {
    console.log("没有修改内容");
    return;
  }
  if (newValue === "") {
    console.warn("输入不能为空");
    newName.value = comeTreeData.name || "新节点";
    return;
  }
@@ -349,10 +327,8 @@
      parentId: parentId || null, // å¦‚果没有父节点,则为 null
    });
  } catch (error) {
    console.error("存储失败", error);
    comeTreeData.name = comeTreeData.name || "新节点";
  }
  console.log("保存成功:", newValue);
};
onMounted(async () => {
@@ -365,12 +341,9 @@
};
const remove = async (node, data) => {
  console.log("删除节点:", data);
  if (!data || !data.id) {
    console.warn("无法删除未定义或无效的节点");
    return;
  }
  console.log("删除节点 ID:", data.id);
  let { code, msg } = await delTree([data.id]);
  if (code !== 200) {
    ElMessage.warning("删除失败, " + msg);
@@ -383,7 +356,6 @@
const append = async (data) => {
  if (data === "") {
    // æ–°å¢žæ ¹èŠ‚ç‚¹
    console.log("新增根节点");
    const newNode = {
      id: Date.now(),
      name: "新节点",
@@ -405,7 +377,6 @@
    const node = treeRef.value?.getNode(nodeKey);
    const isExpanded = node?.expanded; // å¦‚果有子级且未展开,先展开节点
    if (hasChildren && !isExpanded) {
      console.log(treeRef.value, "展开节点", nodeKey);
      if (
        treeRef.value &&
        treeRef.value.store &&
@@ -414,7 +385,6 @@
        treeRef.value.store.nodesMap[nodeKey].expanded = true;
      }
    }
    const newNode = {
      id: Date.now(),
      name: "新子节点",
@@ -453,7 +423,6 @@
  row.value = rows;
  dialogVisible.value = true;
  archiveDialogs.value.editForm(rows); // è°ƒç”¨ç¼–辑方法
  // console.log("编辑行数据:", archiveDialogs.value);
};
// ç§»é™¤æ‡’加载,直接获取数据
@@ -463,11 +432,9 @@
    if (res.code === 200) {
      treeData.value = res.data?.records || res.data || [];
    } else {
      console.error("Failed to fetch tree data:", res.message);
      treeData.value = [];
    }
  } catch (error) {
    console.error("获取树形数据失败:", error);
    treeData.value = [];
  }
};
src/views/archiveManagement/mould/archiveDialog.vue
@@ -103,9 +103,7 @@
};
const editForm = (val) => {
  ruleForm.value = copyForm.value;
  // fileUploadRef.value.editInit(ruleForm.value);
  nextTick(() => {
      // console.log("编辑表单数据:", fileUploadRef.value);
  fileUploadRef.value.editInit(val);
  });
@@ -144,7 +142,6 @@
        type: "error",
        message: error.msg || "操作失败",
      });
      console.error("API è°ƒç”¨å¤±è´¥:", error);
      return;
    }
    // å‘送 emit äº‹ä»¶
@@ -152,7 +149,10 @@
    // å…³é—­å¯¹è¯æ¡†
    centerDialogVisible.value = false;
  } catch (error) {
    console.error("表单验证失败或API调用失败:", error);
    ElMessage({
      type: "error",
      message: error.msg || "操作失败",
    });
  }
};
</script>
src/views/procureMent/components/ProductionDialog.vue
@@ -174,9 +174,8 @@
      value: item.id,
      label: item.coal,
    }));
    console.log(supplyList.value, coalList.value);
  } catch (error) {
    console.error("获取下拉数据失败:", error);
    ElMessage.error("获取下拉数据失败,请稍后重试");
  }
};
@@ -334,7 +333,6 @@
const formRef = ref(null);
// æäº¤è¡¨å•
const handleSubmit = async () => {
  console.log("提交表单", form.value);
  if (!formRef.value) return;
  await formRef.value.validate(async (valid) => {
    if (valid) {
src/views/procureMent/index.vue
@@ -171,13 +171,6 @@
const handleAddEdit = () => {
  addOrEdit.value == "add" ? (title.value = "新增") : (title.value = "编辑");
  title.value = title.value + "采购信息";
  // æ­£ç¡®ä½¿ç”¨å­ç»„ä»¶ref
  if (productionDialogs.value) {
    // è¿™é‡Œå¯ä»¥è°ƒç”¨å­ç»„件的方法
    console.log("子组件实例:", productionDialogs.value.getDropdownData());
  }
  openDialog();
};
// æ‰“开弹窗
src/views/production/components/ProductionDetailsTable.vue
@@ -1,114 +1,140 @@
<template>
  <el-table :data="tableData" :border="border" style="width: 100%">
    <el-table-column label="煤种" min-width="120" >
      <template #default="{ row, $index }" >
        <el-input
          v-model="row.coal"
          placeholder="请输入煤种"
          @input="handleInput('coal', $index, $event)"
        />
  <el-table :data="tableData" :border="border" style="width: 100%">
    <el-table-column label="煤种" min-width="120">
      <template #default="{ row, $index }">
        <el-select
          clearable
          :model-value="getCoalNameById(row.coal) || row.coal"
          placeholder="请选择煤种"
          @change="(value) => handleCoalSelectChange(row, value)"
          filterable
          :key="`coal-select-${$index}-${weekList.length}`"
        >
          <el-option
            v-for="(item, index) of weekList"
            :key="`option-${index}-${item.key}`"
            :label="item.value"
            :value="item.value"
          />
        </el-select>
      </template>
    </el-table-column>
    <el-table-column label="生产数量" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.productionQuantity"
        <el-input
          v-model="row.productionQuantity"
          placeholder="请输入生产数量"
          type="number"
          @input="handleInput('productionQuantity', $index, $event)"
        />
      </template>
    </el-table-column>
    <el-table-column label="人工成本" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.laborCost"
        <el-input
          v-model="row.laborCost"
          placeholder="请输入人工成本"
          type="number"
          @input="handleInput('laborCost', $index, $event)"
        >
          <template #suffix>
            <i style="font-style:normal;">元</i>
            <i style="font-style: normal">元</i>
          </template>
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="能耗成本" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.energyConsumptionCost"
        <el-input
          v-model="row.energyConsumptionCost"
          placeholder="请输入能耗成本"
          type="number"
          @input="handleInput('energyConsumptionCost', $index, $event)"
        >
          <template #suffix>
            <i style="font-style:normal;">元</i>
            <i style="font-style: normal">元</i>
          </template>
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="设备折旧" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.equipmentDepreciation"
        <el-input
          v-model="row.equipmentDepreciation"
          placeholder="请输入设备折旧"
          type="number"
          @input="handleInput('equipmentDepreciation', $index, $event)"
        >
          <template #suffix>
            <i style="font-style:normal;">元</i>
            <i style="font-style: normal">元</i>
          </template>
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="采购单价" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.purchasePrice"
        <el-input
          v-model="row.purchasePrice"
          placeholder="请输入采购单价"
          type="number"
          @input="handleInput('purchasePrice', $index, $event)"
        >
          <template #suffix>
            <i style="font-style:normal;">元</i>
            <i style="font-style: normal">元</i>
          </template>
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="总成本" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.totalCost"
        <el-input
          disabled
          v-model="row.totalCost"
          placeholder="总成本"
          type="number"
          :readonly="autoCalculate"
          @input="handleInput('totalCost', $index, $event)"
        >
          <template #suffix>
            <i style="font-style:normal;">元</i>
            <i style="font-style: normal">元</i>
          </template>
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="生产人" min-width="120">
      <template #default="{ row, $index }">
        <el-input
          v-model="row.producer"
          placeholder="生产人"
          @input="handleInput('producer', $index, $event)"
        />
        <el-select
          clearable
          :model-value="getUserNameById(row.producer) || row.producer"
          placeholder="请选择生产人"
          @change="(value) => handleUserSelectChange(row, value)"
          filterable
          :key="`producer-select-${$index}-${userList.length}`"
        >
          <el-option
            v-for="(item, index) of userList"
            :key="`option-${index}-${item.key}`"
            :label="item.value"
            :value="item.value"
          />
        </el-select>
      </template>
    </el-table-column>
      <el-table-column v-if="showOperations" label="操作" width="120" fixed="right">
    <el-table-column
      v-if="showOperations"
      label="操作"
      width="120"
      fixed="right"
    >
      <template #default="{ $index }">
        <el-button
          type="danger"
          size="small"
        <el-button
          type="danger"
          size="small"
          @click="handleDelete($index)"
          :icon="Delete"
        >
@@ -120,93 +146,251 @@
</template>
<script setup name="ProductionDetailsTable">
import { ref, computed, watch } from 'vue'
import { Delete } from '@element-plus/icons-vue'
import { ref, computed, watch, onMounted, nextTick } from "vue";
import { Delete } from "@element-plus/icons-vue";
import { getCoalFieldList } from "@/api/basicInformation/coalQualityMaintenance";
import { userListAll } from "@/api/publicApi";
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
    default: () => [],
  },
  border: {
    type: Boolean,
    default: false
    default: false,
  },
  showOperations: {
    type: Boolean,
    default: true
    default: true,
  },
  autoCalculate: {
    type: Boolean,
    default: true
  }
})
    default: true,
  },
});
const emit = defineEmits(['update:modelValue', 'input-change', 'delete-row'])
const emit = defineEmits(["update:modelValue", "input-change", "delete-row"]);
// ä½¿ç”¨ v-model è¿›è¡ŒåŒå‘绑定
const tableData = computed({
  get() {
    return props.modelValue
    return props.modelValue;
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
    emit("update:modelValue", value);
  },
});
// å¤„理输入变化
const handleInput = (field, index, value) => {
  const newData = [...tableData.value]
  newData[index][field] = value
  const newData = [...tableData.value];
  newData[index][field] = value;
  // å¦‚果开启自动计算总成本
  if (props.autoCalculate && ['laborCost', 'energyCost', 'equipmentDepreciation', 'purchasePrice'].includes(field)) {
    calculateTotalCost(newData[index])
  if (
    props.autoCalculate &&
    [
      "laborCost",
      "energyCost",
      "equipmentDepreciation",
      "purchasePrice",
    ].includes(field)
  ) {
    calculateTotalCost(newData[index]);
  }
  tableData.value = newData
  emit('input-change', { field, index, value, row: newData[index] })
}
  tableData.value = newData;
  emit("input-change", { field, index, value, row: newData[index] });
};
// è®¡ç®—总成本
const calculateTotalCost = (row) => {
  const laborCost = parseFloat(row.laborCost) || 0
  const energyCost = parseFloat(row.energyCost) || 0
  const equipmentDepreciation = parseFloat(row.equipmentDepreciation) || 0
  const purchasePrice = parseFloat(row.purchasePrice) || 0
  row.totalCost = (laborCost + energyCost + equipmentDepreciation + purchasePrice).toFixed(2)
}
  const laborCost = parseFloat(row.laborCost) || 0;
  const energyCost = parseFloat(row.energyCost) || 0;
  const equipmentDepreciation = parseFloat(row.equipmentDepreciation) || 0;
  const purchasePrice = parseFloat(row.purchasePrice) || 0;
  row.totalCost = (
    laborCost +
    energyCost +
    equipmentDepreciation +
    purchasePrice
  ).toFixed(2);
};
// åˆ é™¤è¡Œ
const handleDelete = (index) => {
  const newData = [...tableData.value]
  newData.splice(index, 1)
  tableData.value = newData
  emit('delete-row', index)
}
  const newData = [...tableData.value];
  newData.splice(index, 1);
  tableData.value = newData;
  emit("delete-row", index);
};
// å¤„理煤种选择变化
// å¤„理煤种选择变化(新方法:名称选择转ID)
const handleCoalSelectChange = (row, selectedName) => {
  // æ ¹æ®é€‰æ‹©çš„名称找到对应的ID
  const coalItem = weekList.value.find(item => item.value === selectedName);
  if (coalItem) {
    row.coal = coalItem.key; // è®¾ç½®ä¸ºID
  } else {
    row.coal = ''; // å¦‚果没找到,清空
  }
};
// æ ¹æ®ID获取煤种名称(用于显示)
const getCoalNameById = (id) => {
  const coal = weekList.value.find(item => item.key == id);
  return coal ? coal.value : id;
};
const weekList = ref([]);
// ç›‘听表格数据变化,确保显示正确
watch(() => props.modelValue, (newValue) => {
  if (newValue && weekList.value.length > 0) {
    // å½“数据加载完成且weekList已获取时,确保显示正确
  }
}, { deep: true });
// ç›‘听weekList变化,当下拉数据加载完成后处理显示
watch(weekList, (newList) => {
  if (newList.length > 0 && tableData.value.length > 0) {
    // å¼ºåˆ¶è§¦å‘表格重新渲染以确保el-select正确显示
    nextTick(() => {
      // è§¦å‘一个微小的数据变化来强制重新渲染
      const tempData = [...tableData.value];
      tableData.value = tempData;
    });
  }
}, { deep: true });
onMounted(async()=>{
  let res = await getCoalFieldList()
  res.data.forEach(item => {
    let obj = {};
    obj.value = item.fieldName;
    obj.key = item.id;
    weekList.value.push(obj);
  });
  let ress = await userListAll();
  ress.data.forEach(item => {
    let obj = {};
    obj.value = item.nickName;
    obj.key = item.userId;
    userList.value.push(obj);
  });
  // é€šçŸ¥çˆ¶ç»„ä»¶weekList已加载完成
  nextTick(() => {
  });
})
const dropdownList = ref([]);
// èŽ·å–ä¸‹æ‹‰æ•°æ®
const getDropdownData = async () => {
  let res = await getCoalFieldList();
  if (res.code === 200) {
    dropdownList.value = res.data.map((item) => ({
      value: item.fieldName,
      key: item.id,
    }));
  } else {
    ElMessage.error("获取下拉数据失败");
  }
};
const userList = ref([]);
const getUserList = (async()=>{
  let res = await userListAll();
  if (res.code === 200) {
    userList.value = res.data.map((item) => ({
      value: item.nickName,
      key: item.userId,
    }));
  } else {
    ElMessage.error("获取用户列表失败");
  }
})
// ç›‘听表格数据变化,确保显示正确
watch(() => props.modelValue, (newValue) => {
  if (newValue && userList.value.length > 0) {
    // å½“数据加载完成且weekList已获取时,确保显示正确
  }
}, { deep: true });
// ç›‘听userList变化,当下拉数据加载完成后处理显示
watch(userList, (newList) => {
  if (newList.length > 0 && tableData.value.length > 0) {
    // å¼ºåˆ¶è§¦å‘表格重新渲染以确保el-select正确显示
    nextTick(() => {
      // è§¦å‘一个微小的数据变化来强制重新渲染
      const tempData = [...tableData.value];
      tableData.value = tempData;
    });
  }
}, { deep: true });
const getUserNameById = (id) => {
  const producer = userList.value.find(item => item.key == id);
  return producer ? producer.value : id;
};
// å¤„理用户选择变化(新方法:名称选择转ID)
const handleUserSelectChange = (row, selectedName) => {
  // æ ¹æ®é€‰æ‹©çš„名称找到对应的ID
  const userItem = userList.value.find(item => item.value === selectedName);
  if (userItem) {
    row.producer = userItem.key; // è®¾ç½®ä¸ºID
  } else {
    row.producer = ''; // å¦‚果没找到,清空
  }
};
// æš´éœ²æ–¹æ³•给父组件使用
defineExpose({
  calculateTotalCost,
  getDropdownData,
  getCoalNameById, // æš´éœ²èŽ·å–ç…¤ç§åç§°çš„æ–¹æ³•
  weekList, // æš´éœ²weekList让父组件可以访问
  addRow: (rowData = {}) => {
    const defaultRow = {
      coal: '',
      calorificValue: '',
      productionQuantity: '',
      laborCost: '',
      energyCost: '',
      equipmentDepreciation: '',
      purchasePrice: '',
      totalCost: '',
      ...rowData
    }
    tableData.value = [...tableData.value, defaultRow]
      coal: "",
      calorificValue: "",
      productionQuantity: "",
      laborCost: "",
      energyCost: "",
      equipmentDepreciation: "",
      purchasePrice: "",
      totalCost: "",
      producer: "",
      ...rowData,
    };
    tableData.value = [...tableData.value, defaultRow];
  },
  clearData: () => {
    tableData.value = []
    tableData.value = [];
  },
  // æ·»åŠ ä¸€ä¸ªæ–¹æ³•æ¥ç­‰å¾…weekList加载完成
  waitForWeekList: () => {
    return new Promise((resolve) => {
      if (weekList.value.length > 0) {
        resolve();
      } else {
        const unwatch = watch(weekList, (newList) => {
          if (newList.length > 0) {
            unwatch();
            resolve();
          }
        });
      }
    });
  },
  // å¼ºåˆ¶åˆ·æ–°è¡¨æ ¼æ˜¾ç¤º
  forceRefresh: () => {
    nextTick(() => {
      const tempData = [...tableData.value];
      tableData.value = tempData;
    });
  }
})
});
</script>
<style scoped>
src/views/production/components/ProductionDetailsTableExample.vue
ÎļþÒÑɾ³ý
src/views/production/components/ProductionDialog.vue
@@ -9,9 +9,7 @@
    <el-row :gutter="10" style="margin-bottom: 10px">
      <el-col :span="3">
        <el-button type="primary" @click="handlData"
          ><el-icon>
            <Plus /> </el-icon
          >选择数据</el-button
          ><el-icon> <Plus /> </el-icon>选择数据</el-button
        >
      </el-col>
      <el-col :span="4">
@@ -57,21 +55,14 @@
            æ–°å¢ž
          </el-button>
        </el-col>
        <el-col :span="2">
        <!-- <el-col :span="2">
          <el-button type="danger" @click="clearAllRows">
            <el-icon>
              <Delete />
            </el-icon>
            æ¸…空
          </el-button>
        </el-col>
        <!-- <el-col :span="2">
        <el-button type="warning" @click="calculateAllCosts">
          <el-icon>
            <Warning />
          </el-icon> é‡æ–°è®¡ç®—
        </el-button>
      </el-col> -->
        </el-col> -->
      </el-row>
      <ProductionDetailsTable
        v-model="detailsTableData"
@@ -85,12 +76,14 @@
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleClose" v-if="dialogType === 'add'"
        <el-button
          @click="handleClose"
          v-if="dialogType === 'add' || dialogType === 'edit'"
          >取 æ¶ˆ</el-button
        >
        <el-button @click="handleReset" v-if="dialogType === 'edit'"
        <!-- <el-button @click="handleReset" v-if="dialogType === 'edit'"
          >重 ç½®</el-button
        >
        > -->
        <el-button type="primary" :loading="loading" @click="handleSubmit"
          >ç¡® å®š</el-button
        >
@@ -114,6 +107,8 @@
      />
    </div>
    <ETable
      :showIndex="false"
      :showOverflowTooltip="false"
      @selection-change="handleSelectionChange"
      :showOperations="false"
      ref="etableRef"
@@ -152,35 +147,45 @@
import ProductionDetailsTable from "./ProductionDetailsTable.vue";
import { ElMessage, ElMessageBox, ElAlert, ElText } from "element-plus";
import { Delete, Warning, Plus } from "@element-plus/icons-vue";
import { getOfficialAll, addOrEditPM } from "@/api/production/index.js";
import { getCurrentInstance } from "vue";
import {
  getOfficialAll,
  addOrEditPM,
  deleteProductionInventory,
} from "@/api/production/index.js";
import { validateFormData, validateNumber, deepClone, createDefaultProductionRow } from "@/utils/production";
import { useCoalData } from "./useCoalData";
import useUserStore from "@/store/modules/user";
// Props å’Œ Emits
const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
  type: {
    type: String,
    default: "add", // 'add' æˆ– 'edit'
  },
  rowData: {
    type: Object,
    default: () => ({}),
  },
  visible: { type: Boolean, default: false },
  type: { type: String, default: "add" },
  rowData: { type: Object, default: () => ({}) },
});
const dialogVisible = defineModel("visible", {
  type: Boolean,
  default: false,
});
const emit = defineEmits(["update:visible", "success"]);
const dialogVisible = defineModel("visible", { type: Boolean, default: false });
const emit = defineEmits(["update:visible", "success", "update:productionAndProcessing"]);
// ç”¨æˆ·ä¿¡æ¯å’Œç…¤ç§æ•°æ®
const userStore = useUserStore();
const { getCoalNameById } = useCoalData();
let userInfo;
// å¯¹è¯æ¡†çŠ¶æ€
const innerVisible = ref(false);
const dialogType = ref("add");
const loading = ref(false);
const formRef = ref(null);
const etableRef = ref(null);
// æ•°æ®çŠ¶æ€
const tableData = ref([]);
const detailsTableData = ref([]);
const formalDatabaseData = ref([]);
const formalDatabaseSelectedData = ref([]);
const selectedIds = ref([]);
const currentRow = ref(null);
const copyForm = ref(null);
// è¡¨æ ¼åˆ—配置
const columns = [
  { label: "煤种", prop: "coal", minwidth: 120 },
  { label: "库存数量", prop: "inventoryQuantity", minwidth: 100 },
@@ -192,9 +197,16 @@
    editType: "number",
  },
];
const etableRef = ref(null);
const selectedIds = ref([]); // é»˜è®¤é€‰ä¸­çš„ID数组
// è°ƒè¯•函数:验证ID匹配逻辑
const formalDatabaseColumns = ref([
  { prop: "supplierName", label: "供应商名称", minwidth: 150 },
  { prop: "coal", label: "煤种类型", minwidth: 60 },
  { prop: "inventoryQuantity", label: "库存数量", minwidth: 80 },
  { prop: "unit", label: "单位", minwidth: 20 },
  { prop: "priceExcludingTax", label: "单价(不含税)", minwidth: 80 },
  { prop: "createTime", label: "登记日期", width: 200 },
]);
// å·¥å…·å‡½æ•°
const debugIdMatching = () => {
  if (formalDatabaseData.value.length > 0 && selectedIds.value.length > 0) {
    const matchedRows = formalDatabaseData.value.filter((row) =>
@@ -202,32 +214,12 @@
    );
  }
};
const detailsTableData = ref([]);
const handleRowClick = (row) => {
  currentRow.value = row;
};
const formalDatabaseColumns = ref([
  { prop: "supplierName", label: "供应商名称", minwidth: 150 },
  { prop: "coal", label: "煤种类型", minwidth: 60 },
  { prop: "inventoryQuantity", label: "库存数量", minwidth: 80 },
  { prop: "unit", label: "单位", minwidth: 100 },
  { prop: "priceExcludingTax", label: "单价(不含税)", minwidth: 80 },
  { prop: "createTime", label: "登记日期", minwidth: 400 },
]);
// è¡¨å•数据
const formData = reactive({
  category: "",
  unit: "",
  productionVolume: 0,
  laborCost: 0,
  materialCost: 0,
  equipmentCost: 0,
  totalCost: 0,
  totalPrice: 0,
  profit: 0,
  reviewer: "",
  date: "",
});
// èŽ·å–é…ç½®æ•°æ®
const handlData = async () => {
  innerVisible.value = true;
  let res = await getOfficialAll();
@@ -258,44 +250,32 @@
  nextTick(() => {
    setTimeout(() => {
      try {
        // å…ˆæ¸…除所有选中
        etableRef.value.clearSelection();
        // æ‰¾åˆ°éœ€è¦é€‰ä¸­çš„行并设置选中状态
        // æ³¨æ„ï¼šids中是officialId,需要匹配formalDatabaseData中的id字段
        const rowsToSelect = formalDatabaseData.value.filter((row) =>
          ids.includes(row.id)
        );
        if (rowsToSelect.length > 0) {
          etableRef.value.setRowsSelection(rowsToSelect, true);
          console.log("选中状态设置完成");
        } else {
        }
      } catch (error) {
        console.error("设置选中状态失败:", error);
      }
    }, 150);
  });
};
const formalDatabaseData = ref([]);
const formalDatabaseSelectedData = ref([]);
formalDatabaseData.value = [];
// åˆå§‹åŒ–
// åˆå§‹åŒ–和编辑初始化
const Initialization = () => {
  tableData.value = [];
  detailsTableData.value = [];
  copyForm.value = null;
  dialogType.value = "add";
};
const copyForm = ref(null);
const editInitialization = (data) => {
  copyForm.value = { ...data }; // æ·±æ‹·è´æ•°æ®
  copyForm.value = deepClone(data);
  tableData.value = data.productionInventoryList || [];
  detailsTableData.value = data.productionList || [];
  dialogType.value = "edit";
  // è®¾ç½®é»˜è®¤é€‰ä¸­çš„ID,使用officialId来匹配
  const existingOfficialIds = tableData.value
    .map((item) => item.officialId)
    .filter((id) => id);
@@ -304,7 +284,6 @@
// ç›‘听对话框状态,在打开时设置选中状态
watch(innerVisible, (newVal) => {
  if (newVal && selectedIds.value.length > 0) {
    console.log("对话框打开,设置选中状态");
    setTimeout(() => setTableSelection(selectedIds.value), 200);
  }
  // å¯¹è¯æ¡†å…³é—­æ—¶æ¸…空选择状态
@@ -344,9 +323,6 @@
    .filter((id) => id);
  selectedIds.value = allOfficialIds;
  console.log("更新后的表格数据:", tableData.value);
  console.log("更新后的选中ID:", selectedIds.value);
  // å…³é—­é€‰æ‹©å¯¹è¯æ¡†
  innerVisible.value = false;
@@ -367,94 +343,99 @@
const handleSelectionChange = (selection) => {
  formalDatabaseSelectedData.value = selection;
};
const handleReset = () => {
  console.log(copyForm.value);
  tableData.value =
    JSON.parse(JSON.stringify(copyForm.value.productionInventoryList)) || [];
  detailsTableData.value =
    JSON.parse(JSON.stringify(copyForm.value.productionList)) || [];
};
// æäº¤è¡¨å•
// æäº¤è¡¨å• - ä½¿ç”¨å·¥å…·å‡½æ•°éªŒè¯
const handleSubmit = async () => {
  let data = {
    productionList: detailsTableData.value,
    productionInventoryList: tableData.value,
    ...copyForm.value,
  };
  let res = await addOrEditPM(data);
  if (res.code === 200) {
    dialogVisible.value = false;
    emit("success");
  } else {
    ElMessage.error("提交失败");
  // éªŒè¯ç”Ÿäº§æ˜Žç»†æ•°æ®
  const detailsValidation = validateFormData(detailsTableData.value, [
    "coal",
    "productionQuantity",
    "laborCost",
    "energyConsumptionCost",
    "equipmentDepreciation",
    "purchasePrice"
  ]);
  if (!detailsValidation.isValid) {
    ElMessage.warning(detailsValidation.message);
    return;
  }
  // éªŒè¯åº“存使用数据
  if (tableData.value.length === 0) {
    ElMessage.warning("请添加生产加工数据");
    return;
  }
  for (let i = 0; i < tableData.value.length; i++) {
    const element = tableData.value[i];
    if (element.usedQuantity == 0 || element.usedQuantity === null) {
      ElMessage.warning(`请填写使用数量: ${element.coal}`);
      return;
    }
  }
  try {
    const data = {
      ...copyForm.value,
      productionList: detailsTableData.value,
      productionInventoryList: tableData.value,
    };
    const res = await addOrEditPM(data);
    if (res.code === 200) {
      dialogVisible.value = false;
      emit("success");
    } else {
      ElMessage.error("提交失败");
    }
  } catch (error) {
    ElMessage.error("提交失败,请重试");
  }
};
// å…³é—­å¼¹çª—
const handleClose = () => {
  dialogVisible.value = false;
  formRef.value?.resetFields();
  Object.assign(formData, {
    category: "",
    unit: "",
    productionVolume: 0,
    laborCost: 0,
    materialCost: 0,
    equipmentCost: 0,
    totalCost: 0,
    totalPrice: 0,
    profit: 0,
    reviewer: "",
    date: "",
  });
};
// æ·»åŠ å•å…ƒæ ¼ç¼–è¾‘å¤„ç†å‡½æ•°
// ä½¿ç”¨æ•°é‡éªŒè¯ - ä½¿ç”¨å·¥å…·å‡½æ•°
const handleCellEdit = (row, prop, value) => {
  if (prop === "usedQuantity") {
    const numValue = Number(value);
    const inventory = Number(row.inventoryQuantity);
    // éªŒè¯è¾“入值
    if (isNaN(numValue) || numValue < 0) {
      ElMessage.warning("使用数量必须是非负数!");
      row.usedQuantity = 0;
    const validation = validateNumber(value, 0, Number(row.inventoryQuantity));
    if (!validation.isValid) {
      ElMessage.warning(validation.message);
      row.usedQuantity = validation.value;
      return;
    }
    if (numValue > inventory) {
      ElMessage.warning(`使用数量不能大于库存数量(${inventory})!`);
      row.usedQuantity = inventory;
      return;
    }
    // æ›´æ–°å€¼
    row.usedQuantity = numValue;
    console.log(`更新 ${row.coal} çš„使用数量为: ${numValue}`);
    row.usedQuantity = validation.value;
  }
};
// å¤„理生产明细表格的操作
// å¤„理生产明细表格的操作 - ä½¿ç”¨å·¥å…·å‡½æ•°
const addNewRow = () => {
  detailsTableData.value.push({
    coal: "",
    productionQuantity: "",
    laborCost: "",
    energyConsumptionCost: "",
    equipmentDepreciation: "",
    purchasePrice: "",
    autoCalculate: "0.00",
    producer: "",
  });
  const newRow = createDefaultProductionRow(userInfo);
  detailsTableData.value.push(newRow);
};
const clearAllRows = () => {
  detailsTableData.value = [];
  ElMessage.success("已清空所有数据");
// é‡ç½®æ•°æ® - ä½¿ç”¨æ·±æ‹·è´
const handleReset = () => {
  if (copyForm.value) {
    tableData.value = deepClone(copyForm.value.productionInventoryList) || [];
    detailsTableData.value = deepClone(copyForm.value.productionList) || [];
  }
};
// èŽ·å–ç”¨æˆ·ä¿¡æ¯
onMounted(async () => {
  try {
    userInfo = await userStore.getInfo();
  } catch (error) {
    ElMessage.error("获取用户信息失败,请重试");
  }
});
// ç®€åŒ–的事件处理函数
const handleDetailsChange = (data) => {
  console.log("生产明细数据变化:", data);
};
const handleDeleteRow = (index) => {
@@ -463,7 +444,6 @@
// åˆ é™¤å•个已选数据项
const handleRemoveItem = (row) => {
  console.log("删除项:", row);
  const index = tableData.value.findIndex(
    (item) => item.officialId === row.officialId
  );
@@ -475,10 +455,6 @@
      .map((item) => item.officialId)
      .filter((id) => id);
    selectedIds.value = updatedOfficialIds;
    console.log("删除后的表格数据:", tableData.value);
    console.log("更新后的选中ID:", selectedIds.value);
    ElMessage.success("已删除选中项");
  }
};
@@ -489,21 +465,26 @@
    ElMessage.warning("没有可清空的数据");
    return;
  }
  ElMessageBox.confirm("确认清空所有已选择的数据吗?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
    .then(async () => {
      if (dialogType.value === "edit") {
        let res = await deleteProductionInventory({
          productionInventoryList: tableData.value,
        });
        emit("update:productionAndProcessing", tableData.value, copyForm.value);
      }
      // [Vue warn]: Component emitted event "update:productionAndProcessing" but it is neither declared in the emits option nor as an "onUpdate:productionAndProcessing" prop.
      formalDatabaseSelectedData.value = [];
      tableData.value = [];
      selectedIds.value = [];
      console.log("已清空所有已选数据");
      ElMessage.success("已清空所有数据");
    })
    .catch(() => {
      console.log("取消清空操作");
    });
    .catch(() => {});
};
// è®¡ç®—总使用量
src/views/production/components/useCoalData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
/**
 * ç…¤ç§æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›ç…¤ç§æ•°æ®çš„获取、缓存、转换等功能
 */
import { ref, computed, watch } from 'vue';
import { getCoalFieldList } from '@/api/basicInformation/coalQualityMaintenance';
import { ElMessage } from 'element-plus';
// å…¨å±€ç…¤ç§æ•°æ®ç¼“å­˜
const coalData = ref([]);
const isLoading = ref(false);
const isLoaded = ref(false);
export function useCoalData() {
  // èŽ·å–ç…¤ç§æ•°æ®
  const getCoalData = async (forceRefresh = false) => {
    if (isLoaded.value && !forceRefresh) {
      return coalData.value;
    }
    if (isLoading.value) {
      // å¦‚果正在加载,等待加载完成
      return new Promise((resolve) => {
        const unwatch = watch(isLoading, (loading) => {
          if (!loading) {
            unwatch();
            resolve(coalData.value);
          }
        });
      });
    }
    isLoading.value = true;
    try {
      const res = await getCoalFieldList();
      if (res.code === 200) {
        coalData.value = res.data;
        isLoaded.value = true;
        return coalData.value;
      } else {
        ElMessage.error('获取煤种数据失败');
        return [];
      }
    } catch (error) {
      ElMessage.error('获取煤种数据失败');
      console.error('煤种数据获取错误:', error);
      return [];
    } finally {
      isLoading.value = false;
    }
  };
  // æ ¹æ®ID获取煤种名称
  const getCoalNameById = (id) => {
    if (!id || coalData.value.length === 0) return id;
    const coal = coalData.value.find(item => item.id == id);
    return coal ? coal.fieldName : id;
  };
  // æ ¹æ®åç§°èŽ·å–ç…¤ç§ID
  const getCoalIdByName = (name) => {
    if (!name || coalData.value.length === 0) return '';
    const coal = coalData.value.find(item => item.fieldName === name);
    return coal ? coal.id : '';
  };
  // ç”Ÿæˆä¸‹æ‹‰é€‰é¡¹
  const coalOptions = computed(() => {
    return coalData.value.map(item => ({
      label: item.fieldName,
      value: item.fieldName,
      key: item.id
    }));
  });
  // ç”Ÿæˆkey-value映射
  const coalMap = computed(() => {
    const map = {};
    coalData.value.forEach(item => {
      map[item.id] = item.fieldName;
    });
    return map;
  });
  return {
    coalData: computed(() => coalData.value),
    coalOptions,
    coalMap,
    isLoading: computed(() => isLoading.value),
    isLoaded: computed(() => isLoaded.value),
    getCoalData,
    getCoalNameById,
    getCoalIdByName
  };
}
src/views/production/components/useDialog.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
/**
 * å¯¹è¯æ¡†ç®¡ç†ç»„合式函数
 * æä¾›å¯¹è¯æ¡†çš„æ‰“开、关闭、数据处理等功能
 */
import { ref } from 'vue';
export function useDialog() {
  const dialogVisible = ref(false);
  const dialogType = ref('add');
  const dialogRef = ref(null);
  const currentRowData = ref(null);
  // æ‰“开对话框
  const openDialog = (type = 'add', rowData = null) => {
    dialogType.value = type;
    currentRowData.value = rowData;
    dialogVisible.value = true;
    // è°ƒç”¨å¯¹è¯æ¡†ç»„件的初始化方法
    if (dialogRef.value) {
      if (type === 'add') {
        dialogRef.value.Initialization?.();
      } else if (type === 'edit' && rowData) {
        dialogRef.value.editInitialization?.(rowData);
      }
    }
  };
  // å…³é—­å¯¹è¯æ¡†
  const closeDialog = () => {
    dialogVisible.value = false;
    dialogType.value = 'add';
    currentRowData.value = null;
  };
  // å¯¹è¯æ¡†æˆåŠŸå›žè°ƒ
  const handleDialogSuccess = (callback) => {
    closeDialog();
    if (typeof callback === 'function') {
      callback();
    }
  };
  return {
    // çŠ¶æ€
    dialogVisible,
    dialogType,
    dialogRef,
    currentRowData,
    // æ–¹æ³•
    openDialog,
    closeDialog,
    handleDialogSuccess
  };
}
src/views/production/components/useTableData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,136 @@
/**
 * è¡¨æ ¼æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›åˆ†é¡µã€æœç´¢ã€é€‰æ‹©ç­‰é€šç”¨åŠŸèƒ½
 */
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
export function useTableData(apiFunction, options = {}) {
  const {
    pageSize = 10,
    searchField = 'searchAll'
  } = options;
  // å“åº”式数据
  const tableData = ref([]);
  const loading = ref(false);
  const total = ref(0);
  const selectedRows = ref([]);
  // æŸ¥è¯¢å‚æ•°
  const queryParams = reactive({
    [searchField]: '',
    current: 1,
    size: pageSize,
  });
  // èŽ·å–åˆ—è¡¨æ•°æ®
  const getList = async () => {
    loading.value = true;
    try {
      const params = {
        [searchField]: queryParams[searchField],
        current: queryParams.current,
        size: queryParams.size,
        page: queryParams.current,
        pageSize: queryParams.size,
        pageNum: queryParams.current,
        limit: queryParams.size,
        offset: (queryParams.current - 1) * queryParams.size
      };
      const res = await apiFunction(params);
      tableData.value = res.data.records || [];
      total.value = res.data.total || 0;
    } catch (error) {
      ElMessage.error('获取数据失败');
      console.error('API错误:', error);
    } finally {
      loading.value = false;
    }
  };
  // æœç´¢
  const handleSearch = () => {
    queryParams.current = 1;
    getList();
  };
  // é‡ç½®æœç´¢
  const handleReset = () => {
    queryParams[searchField] = '';
    handleSearch();
  };
  // åˆ†é¡µå¤„理
  const handlePageChange = ({ page, limit }) => {
    if (page && page !== queryParams.current) {
      queryParams.current = page;
    }
    if (limit && limit !== queryParams.size) {
      queryParams.size = limit;
      queryParams.current = 1; // æ”¹å˜æ¯é¡µå¤§å°æ—¶å›žåˆ°ç¬¬ä¸€é¡µ
    }
    getList();
  };
  // è¡¨æ ¼é€‰æ‹©å¤„理
  const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  };
  // æ‰¹é‡åˆ é™¤
  const deleteSelected = async (deleteFunction) => {
    if (selectedRows.value.length === 0) {
      ElMessage.warning('请选择要删除的数据');
      return;
    }
    try {
      await ElMessageBox.confirm(
        `确认删除选中的 ${selectedRows.value.length} æ¡æ•°æ®å—?`,
        '删除确认',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }
      );
      const ids = selectedRows.value.map(row => row.id);
      await deleteFunction(ids);
      ElMessage.success('删除成功');
      selectedRows.value = [];
      getList();
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error('删除失败');
        console.error('删除错误:', error);
      }
    }
  };
  // åˆ·æ–°æ•°æ®
  const refresh = () => {
    getList();
  };
  return {
    // æ•°æ®
    tableData,
    loading,
    total,
    selectedRows,
    queryParams,
    // æ–¹æ³•
    getList,
    handleSearch,
    handleReset,
    handlePageChange,
    handleSelectionChange,
    deleteSelected,
    refresh
  };
}
src/views/production/index.vue
@@ -22,59 +22,64 @@
        <el-button type="success" :icon="Plus" @click="openDialog('add')">
          æ–°å¢žåŠ å·¥
        </el-button>
        <el-button type="danger" :icon="Delete" :disabled="!selectedRows.length">
        <el-button type="danger" :icon="Delete" :disabled="!selectedRows.length" @click="() => deleteSelected(delPM)">
          åˆ é™¤
        </el-button>
      </div>
      <!-- æ•°æ®è¡¨æ ¼ -->
      </div>      <!-- æ•°æ®è¡¨æ ¼ -->
      <ETable
        :showOverflowTooltip="false"
        :loading="loading"
        :table-data="tableData"
        :columns="columns"
        :current-page="queryParams.current"
        :page-size="queryParams.size"
        @selection-change="handleSelectionChange"
        @edit="row => openDialog('edit', row)"
        :show-selection="true"
        :border="true"
        :maxHeight="480"
      >
        <template #coal="{ row }">
      >        <template #coal="{ row }">
          <div class="coal-tags">
            <el-tag v-for="coal in parseCoalArray(row.coal)" :key="coal" size="small">
              {{ coal }}
              {{ getCoalNameById(coal) }}
            </el-tag>
            <span v-if="!row.coal">--</span>
          </div>
        </template>
      </ETable>
      <!-- åˆ†é¡µç»„ä»¶ -->
      </ETable>      <!-- åˆ†é¡µç»„ä»¶ -->
      <Pagination
        :layout="'total, prev, pager, next, jumper'"
        :total="total"
        :page="queryParams.current"
        v-model:page="queryParams.current"
        :limit="queryParams.size"
        @pagination="handlePageChange"
      />
    </el-card>
    <!-- ç”Ÿäº§å¯¹è¯æ¡† -->
    <!-- handleProductionAndProcessing -->
    <ProductionDialog
      v-model:visible="dialogVisible"
      ref="dialogRef"
      :type="dialogType"
      @update:productionAndProcessing="handleProductionAndProcessing"
      @success="handleDialogSuccess"
    />
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Plus, Delete } from "@element-plus/icons-vue";
import ProductionDialog from "./components/ProductionDialog.vue";
import ETable from "@/components/Table/ETable.vue";
import Pagination from "@/components/Pagination/index.vue";
import { getProductionMasterList } from "@/api/production";
import { getProductionMasterList, delPM } from "@/api/production";
import { parseCoalArray } from "@/utils/production";
import { useTableData } from "./components/useTableData.js";
import { useDialog } from "./components/useDialog.js";
import { useCoalData } from "./components/useCoalData.js";
// è¡¨æ ¼åˆ—配置
const columns = [
@@ -87,115 +92,53 @@
  { prop: "producer", label: "生产人", minWidth: 150 },
];
// å“åº”式数据
const tableData = ref([]);
const loading = ref(false);
const total = ref(0);
const selectedRows = ref([]);
const dialogVisible = ref(false);
const dialogType = ref("add");
const dialogRef = ref(null);
// ä½¿ç”¨è¡¨æ ¼æ•°æ®ç»„合式函数
const {
  tableData,
  loading,
  total,
  selectedRows,
  queryParams,
  getList,
  handleSearch,
  handleReset,
  handlePageChange,
  handleSelectionChange,
  deleteSelected
} = useTableData(getProductionMasterList, { pageSize: 10 });
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({
  searchAll: "",
  current: 1,
  size: 10,
});
// ä½¿ç”¨å¯¹è¯æ¡†ç»„合式函数
const {
  dialogVisible,
  dialogType,
  dialogRef,
  openDialog,
  handleDialogSuccess: onDialogSuccess
} = useDialog();
// èŽ·å–è¡¨æ ¼æ•°æ®
// èŽ·å–è¡¨æ ¼æ•°æ®
const getList = async () => {
  loading.value = true;
  try {
    // æž„建正确的分页参数
    const params = {
      searchAll: queryParams.searchAll,
      // å°è¯•多种常见的分页参数格式
      current: queryParams.current,
      size: queryParams.size,
      page: queryParams.current,
      pageSize: queryParams.size,
      pageNum: queryParams.current,
      limit: queryParams.size,
      offset: (queryParams.current - 1) * queryParams.size
    };
    console.log('发送分页参数:', params);
    console.log(`第${queryParams.current}页应该显示第${(queryParams.current - 1) * queryParams.size + 1}-${queryParams.current * queryParams.size}条数据`);
    const res = await getProductionMasterList(params);
    tableData.value = res.data.records || [];
    total.value = res.data.total || 0;
    console.log('接收到的数据:', {
      å½“前页: queryParams.current,
      è¿”回条数: tableData.value.length,
      æ€»æ¡æ•°: total.value
    });
  } catch (error) {
    ElMessage.error("获取数据失败");
    console.error('API错误:', error);
  } finally {
    loading.value = false;
  }
};
// ä½¿ç”¨ç…¤ç§æ•°æ®ç»„合式函数
const { getCoalNameById, getCoalData } = useCoalData();
// æœç´¢å’Œé‡ç½®
const handleSearch = () => {
  queryParams.current = 1;
  getList();
};
const handleReset = () => {
  queryParams.searchAll = "";
  handleSearch();
};
// åˆ†é¡µå¤„理
const handlePageChange = ({ page }) => {
  queryParams.current = page;
  getList();
};
// è¡¨æ ¼é€‰æ‹©å¤„理
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开对话框 - ç»Ÿä¸€å¤„理新增和编辑
const openDialog = (type, row = null) => {
  dialogType.value = type;
  dialogVisible.value = true;
  if (type === 'add') {
    dialogRef.value?.Initialization();
  } else if (type === 'edit' && row) {
    dialogRef.value?.editInitialization({ ...row });
// å¤„理生产数据更新
const handleProductionAndProcessing = (row, rows) => {
  const index = tableData.value.findIndex(item => item.id === rows.id);
  if (index !== -1) {
    tableData.value[index] = { ...tableData.value[index], ...row };
  }
};
// å¯¹è¯æ¡†æˆåŠŸå›žè°ƒ
const handleDialogSuccess = () => {
  getList();
  ElMessage.success("操作成功");
  onDialogSuccess(() => {
    getList();
    ElMessage.success("操作成功");
  });
};
// è§£æžç…¤ç§æ•°ç»„ - ç®€åŒ–逻辑
const parseCoalArray = (coalString) => {
  if (!coalString) return [];
  if (Array.isArray(coalString)) return coalString;
  return String(coalString)
    .replace(/^\[|\]$/g, '')
    .split(',')
    .map(item => item.trim())
    .filter(Boolean);
};
// ç»„件挂载时加载数据
onMounted(getList);
onMounted(async () => {
  await getCoalData(); // é¢„加载煤种数据
  getList();
});
</script>
<style scoped lang="scss">