maven
5 小时以前 7ffa19f1fe3b37519e83ed1f86715154b13c00f3
yys  生产管控(完成基础逻辑)
已修改5个文件
已添加12个文件
2361 ■■■■■ 文件已修改
src/api/productionScheduling/index.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/ETable.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/components/ProductionDialog.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procureMent/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/components/ProductionDetailsTable.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/components/ProductionDetailsTable.vue 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/components/ProductionDialog.vue 410 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/components/useCoalData.js 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/components/useDialog.js 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/components/useTableData.js 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/operationScheduling/index.vue 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/productionReporting/components/ProductionDialog.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/productionReporting/components/useCoalData.js 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/productionReporting/components/useDialog.js 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/productionReporting/components/useTableData.js 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/production/productionReporting/index.vue 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouseManagement/index.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionScheduling/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// ç”Ÿäº§ä»»åŠ¡ï¼ŒæŠ¥å·¥
import request from '@/utils/request'
// æŸ¥è¯¢ç”Ÿäº§åŠ å·¥-正式库煤种列表
export function listPage(query) {
    return request({
        url: '/productionScheduling/list',
        method: 'get',
        params: query
    })
}
// æŽ’产
export function addProductionScheduling(data) {
    return request({
        url: '/productionScheduling/addProductionScheduling',
        method: 'post',
        data: data
    })
}
// åˆ é™¤
export function delProductionScheduling(data) {
    return request({
        url: '/productionScheduling/delProductionScheduling',
        method: 'delete',
        data: data
    })
}
//报工
export function work(data) {
    return request({
        url: '/productionScheduling/work',
        method: 'post',
        data: data
    })
}
src/components/Table/ETable.vue
@@ -53,6 +53,10 @@
          <!-- é»˜è®¤æ“ä½œæŒ‰é’® -->
          <el-button v-if="operations.includes('edit')" link type="primary" size="small"
            @click="handleEdit(scope.row)">编辑</el-button>
          <el-button v-if="operations.includes('scheduling') && scope.row.status != 3" link type="primary" size="small"
                     @click="handleEdit(scope.row)">排产</el-button>
          <el-button v-if="operations.includes('work') && scope.row.status != 3" link type="primary" size="small"
                     @click="handleEdit(scope.row)">报工</el-button>
          <el-button v-if="operations.includes('viewRow')" link type="primary" size="small"
            @click="handleView(scope.row)">查看</el-button>
          <el-button v-if="operations.includes('viewFile')" link type="primary" size="small"
src/views/procureMent/components/ProductionDialog.vue
@@ -30,6 +30,20 @@
            />
          </el-select>
        </el-form-item>
        <el-form-item label="煤料类型" prop="type">
          <el-select
              v-model="form.type"
              placeholder="请选择煤种"
              :disabled="isViewMode"
          >
            <el-option
                :label="item.label"
                v-for="item in typeList"
                :key="item.value"
                :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="煤种" prop="coalId">
          <el-select
            v-model="form.coalId"
@@ -183,6 +197,16 @@
  required: true,
  type: Boolean,
});
const typeList = [
  {
    value: 1,
    label: "成品",
  },
  {
    value: 2,
    label: "原料",
  },
];
const form = defineModel("form", {
  required: true,
  type: Object,
@@ -343,6 +367,7 @@
  supplierName: [
    { required: true, message: "请输入供应商名称", trigger: "blur" },
  ],
  type: [{ required: true, message: "请选择类型", trigger: "change" }],
  coal: [{ required: true, message: "请输入煤种", trigger: "blur" }],
  purchaseQuantity: [
    { required: true, message: "请输入采购数量", trigger: "blur" },
src/views/procureMent/index.vue
@@ -145,6 +145,14 @@
    },
  },
  {
    prop: "type",
    label: "煤料类型",
    minWidth: 120,
    formatter: (row) => {
      return row.type === 1 ? "成品" : "原料";
    },
  },
  {
    prop: "coalId",
    label: "煤种类型",
    minWidth: 120,
src/views/production/components/ProductionDetailsTable.vue
@@ -1,5 +1,24 @@
<template>
  <el-table :data="tableData" :border="border" style="width: 100%">
    <el-table-column label="煤料类型" min-width="120">
      <template #default="{ row, $index }">
        <el-select
            clearable
            v-model="row.type"
            placeholder="请选择煤料类型"
            filterable
            :key="`coalId-select-${$index}-${typeList.length}`"
            :disabled="isViewMode"
        >
          <el-option
              v-for="(item, index) of typeList"
              :key="`option-${index}-${item.value}`"
              :label="item.label"
              :value="item.value"
          />
        </el-select>
      </template>
    </el-table-column>
    <el-table-column label="煤种" min-width="120">
      <template #default="{ row, $index }">
        <el-select
@@ -164,6 +183,16 @@
import {getCoalInfoList} from "@/api/production";
import {userListAll} from "@/api/publicApi";
const typeList = [
  {
    label: "成品",
    value: 1,
  },
  {
    label: "原料",
    value: 2,
  }
  ]
const props = defineProps({
  modelValue: {
    type: Array,
@@ -387,6 +416,7 @@
      purchasePrice: "",
      totalCost: "",
      producerId: "",
      type: 1,
      ...rowData,
    };
    tableData.value = [...tableData.value, defaultRow];
src/views/production/operationScheduling/components/ProductionDetailsTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,360 @@
<template>
  <el-table :data="tableData" :border="border" style="width: 100%">
    <el-table-column label="工序" min-width="120">
      <template #default="{ row, $index }">
        <el-select
            clearable
            v-model="row.process"
            placeholder="请选择工序"
            filterable
            :disabled="isViewMode"
        >
          <el-option
              v-for="(item, index) of process_list"
              :label="item.label"
              :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.unit"
            placeholder="请输入单位"
            :disabled="isViewMode"
        />
      </template>
    </el-table-column>
    <el-table-column label="排产数量" min-width="120">
      <template #default="{ row, $index }">
        <el-input
            v-model="row.schedulingNum"
            placeholder="请输入排产数量"
            type="number"
            :disabled="isViewMode"
        />
      </template>
    </el-table-column>
    <el-table-column label="工时定额" min-width="120">
      <template #default="{ row, $index }">
        <el-input
            v-model="row.workHours"
            placeholder="请输入工时定额"
            type="number"
            @input="handleInput('workHours', $index, $event)"
            :disabled="isViewMode"
        >
        </el-input>
      </template>
    </el-table-column>
    <el-table-column label="排产日期" min-width="120">
      <template #default="{ row, $index }">
        <el-date-picker
            v-model="row.schedulingDate"
            type="datetime"
            clearable
            placeholder="选择日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
        />
      </template>
    </el-table-column>
    <el-table-column label="排产人" min-width="120">
      <template #default="{ row, $index }">
        <el-select
            clearable
            v-model="row.schedulingUserId"
            placeholder="请选择排产人"
            filterable
            :key="`producer-select-${$index}-${userList.length}`"
            :disabled="isViewMode"
        >
          <el-option
              v-for="(item, index) of userList"
              :key="`option-${index}-${item.key}`"
              :label="item.value"
              :value="item.key"
          />
        </el-select>
      </template>
    </el-table-column>
    <el-table-column
        label="操作"
        width="120"
        fixed="right"
        v-if="dialogType !== 'viewRow'"
    >
      <template #default="{ $index }">
        <el-button
            type="danger"
            size="small"
            @click="handleDelete($index)"
            :icon="Delete"
        >
          åˆ é™¤
        </el-button>
      </template>
    </el-table-column>
  </el-table>
</template>
<script setup name="ProductionDetailsTable">
import {ref, computed, watch, onMounted, nextTick} from "vue";
import {Delete} from "@element-plus/icons-vue";
import {ElMessage} from "element-plus";
import {getCoalFieldList} from "@/api/basicInformation/coalQualityMaintenance";
import {userListAll} from "@/api/publicApi";
const { proxy } = getCurrentInstance();
const {process_list} = proxy.useDict("process_list");
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => [],
  },
  border: {
    type: Boolean,
    default: false,
  },
  showOperations: {
    type: Boolean,
    default: true,
  },
  autoCalculate: {
    type: Boolean,
    default: true,
  },
  dialogType:{
    type: String,
    default:'add'
  }
});
const isViewMode = computed(() => props.dialogType === "viewRow");
const emit = defineEmits(["update:modelValue", "input-change", "delete-row"]);
// ä½¿ç”¨ v-model è¿›è¡ŒåŒå‘绑定
const tableData = computed({
  get() {
    return props.modelValue;
  },
  set(value) {
    emit("update:modelValue", value);
  },
});
// å¤„理输入变化
const handleInput = (field, index, value) => {
  // ç¡®ä¿è¾“入值是数字或空字符串而且非负数
  if (!/^\d*\.?\d*$/.test(value) && value !== "") {
    ElMessage.error("请输入有效的数字");
    return;
  }
  const newData = [...tableData.value];
  newData[index][field] = value;
  // å¦‚果开启自动计算总成本
  if (
      props.autoCalculate &&
      [
        "laborCost",
        "energyCost",
        "equipmentDepreciation",
        "purchasePrice",
      ].includes(field)
  ) {
    calculateTotalCost(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.energyConsumptionCost) || 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);
};
// å¤„理煤种选择变化
// å¤„理煤种选择变化(新方法:名称选择转ID)
const handleCoalSelectChange = (row, selectedName) => {
  // æ ¹æ®é€‰æ‹©çš„名称找到对应的ID
  const coalItem = weekList.value.find(item => item.value === selectedName);
  if (coalItem) {
    row.coalId = coalItem.key; // è®¾ç½®ä¸ºID
  } else {
    row.coalId = ''; // å¦‚果没找到,清空
  }
};
// æ ¹æ®ID获取煤种名称(用于显示)
const getCoalNameById = (id) => {
  const coalId = weekList.value.find(item => item.key == id);
  return coalId ? coalId.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 ress = await userListAll();
  ress.data.forEach(item => {
    let obj = {};
    obj.value = item.nickName;
    obj.key = item.userId;
    userList.value.push(obj);
  });
})
const dropdownList = ref([]);
// èŽ·å–ä¸‹æ‹‰æ•°æ®
const getDropdownData = async () => {
  let res = await getCoalFieldList();
  if (res.code === 200) {
    dropdownList.value = res.data.map((item) => ({
      value: item.coal,
      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) => {
  console.log("handleUserSelectChange", row, selectedName);
  // æ ¹æ®é€‰æ‹©çš„名称找到对应的ID
  const userItem = userList.value.find(item => item.value === selectedName);
  if (userItem) {
    row.producerId = userItem.key; // è®¾ç½®ä¸ºID
  } else {
    row.producerId = ''; // å¦‚果没找到,清空
  }
};
// æš´éœ²æ–¹æ³•给父组件使用
defineExpose({
  calculateTotalCost,
  getDropdownData,
  getUserList,
  getCoalNameById, // æš´éœ²èŽ·å–ç…¤ç§åç§°çš„æ–¹æ³•
  weekList, // æš´éœ²weekList让父组件可以访问
  addRow: (rowData = {}) => {
    const defaultRow = {
      process: "工序1",
      unit: "项",
      schedulingNum: "",
      workHours: "",
      schedulingDate: "",
      schedulingUserId: "",
      schedulingUserName: ""
    };
    tableData.value = [...tableData.value, defaultRow];
  },
  clearData: () => {
    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>
:deep(.el-table .el-table__cell) {
  padding: 8px 0;
}
</style>
src/views/production/operationScheduling/components/ProductionDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,410 @@
<template>
  <el-dialog
      v-model="dialogVisible"
      title="工序排产"
      width="1200px"
      :close-on-click-modal="false"
      @close="handleClose"
  >
    <div class="empty-table">
      <el-row :gutter="10">
        <el-col :span="2">
          <el-button type="primary" @click="addNewRow">
            <el-icon>
              <Plus/>
            </el-icon>
            æ–°å¢ž
          </el-button>
        </el-col>
        <el-col :span="4">
          <div style="font-size: 16px;">待排产数量:{{productionQuantity}}</div>
        </el-col>
        <!-- <el-col :span="2">
          <el-button type="danger" @click="clearAllRows">
            <el-icon>
              <Delete />
            </el-icon>
            æ¸…空
          </el-button>
        </el-col> -->
      </el-row>
      <ProductionDetailsTable
          v-model="detailsTableData"
          :border="false"
          :show-operations="dialogType !== 'viewRow'"
          :auto-calculate="true"
          @input-change="handleDetailsChange"
          @delete-row="handleDeleteRow"
          :dialogType="dialogType"
      />
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button
            @click="handleClose"
        >{{ dialogType === 'viewRow' ? '关 é—­' : '取 æ¶ˆ' }}
        </el-button
        >
        <!-- <el-button @click="handleReset" v-if="dialogType === 'edit'"
          >重 ç½®</el-button
        > -->
        <el-button
            v-if="dialogType !== 'viewRow'"
            type="primary"
            :loading="loading"
            @click="handleSubmit"
        >ç¡® å®š
        </el-button
        >
      </div>
    </template>
  </el-dialog>
  <el-dialog
      v-model="innerVisible"
      width="1000"
      title="选择配置数据"
      center
      append-to-body
  >
    <div style="margin-bottom: 10px">
      <el-alert
          v-if="tableData.length > 0"
          :title="`当前已选择 ${tableData.length} æ¡æ•°æ®`"
          type="info"
          :closable="false"
          show-icon
      />
    </div>
    <ETable
        :showIndex="false"
        :showOverflowTooltip="false"
        @selection-change="handleSelectionChange"
        :showOperations="false"
        ref="etableRef"
        :tableData="formalDatabaseData"
        :defaultSelectedIds="selectedIds"
        :rowKey="'id'"
        height="400"
        @cell-edit="handleCellEdit"
        :show-selection="true"
    />
    <el-row :gutter="24" style="margin-top: 15px">
      <el-col :span="12">
        <el-text type="info">
          å·²é€‰æ‹© {{ formalDatabaseSelectedData.length }} æ¡æ•°æ®
        </el-text>
      </el-col>
      <el-col :span="12" style="text-align: right">
        <el-button @click="innerVisible = false">取消</el-button>
        <el-button
            type="primary"
            @click="handleSelectData"
            :disabled="formalDatabaseSelectedData.length === 0"
        >
          ç¡®å®šæ·»åŠ 
        </el-button>
      </el-col>
    </el-row>
  </el-dialog>
</template>
<script setup>
import {ref, reactive, watch, onMounted, nextTick, computed} from "vue";
import ETable from "@/components/Table/ETable.vue";
import ETableModify from "@/components/Table/EtableModify.vue";
import ProductionDetailsTable from "./ProductionDetailsTable.vue";
import {ElMessage, ElMessageBox, ElAlert, ElText} from "element-plus";
import {Delete, Warning, Plus} from "@element-plus/icons-vue";
import {validateFormData, validateNumber, deepClone, createDefaultProductionRow} from "@/utils/production";
import {useCoalData} from "./useCoalData";
import useUserStore from "@/store/modules/user";
import {addProductionScheduling} from '@/api/productionScheduling/index'
// Props å’Œ Emits
const props = defineProps({
  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", "update:productionAndProcessing"]);
// ç”¨æˆ·ä¿¡æ¯å’Œç…¤ç§æ•°æ®
const userStore = useUserStore();
const {getCoalNameById} = useCoalData();
let userInfo;
// å¯¹è¯æ¡†çŠ¶æ€
const innerVisible = ref(false);
const dialogType = ref("add");
const loading = ref(false);
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 coalList = ref([])
const supplierList = ref([]);
const productionQuantity = ref(0);
// å·¥å…·å‡½æ•°
const debugIdMatching = () => {
  if (formalDatabaseData.value.length > 0 && selectedIds.value.length > 0) {
    const matchedRows = formalDatabaseData.value.filter((row) =>
        selectedIds.value.includes(row.id)
    );
  }
};
const handleRowClick = (row) => {
  currentRow.value = row;
};
// æ‰‹åŠ¨è®¾ç½®è¡¨æ ¼é€‰ä¸­çŠ¶æ€
const setTableSelection = (ids) => {
  if (!etableRef.value || !Array.isArray(ids) || ids.length === 0) {
    return;
  }
  nextTick(() => {
    setTimeout(() => {
      try {
        etableRef.value.clearSelection();
        const rowsToSelect = formalDatabaseData.value.filter((row) =>
            ids.includes(row.id)
        );
        if (rowsToSelect.length > 0) {
          etableRef.value.setRowsSelection(rowsToSelect, true);
        }
      } catch (error) {
      }
    }, 150);
  });
};
// åˆå§‹åŒ–和编辑初始化
const Initialization = async () => {
  tableData.value = [];
  detailsTableData.value = [];
  copyForm.value = null;
  dialogType.value = "add";
};
const editInitialization = async (type, data) => {
  productionQuantity.value = data.productionQuantity;
  copyForm.value = deepClone(data);
  tableData.value = data.productionInventoryList || [];
  detailsTableData.value = data.productionList || [];
  dialogType.value = type;
  const existingOfficialIds = tableData.value
      .map((item) => item.officialId)
      .filter((id) => id);
  selectedIds.value = existingOfficialIds;
};
// ç›‘听对话框状态,在打开时设置选中状态
watch(innerVisible, (newVal) => {
  if (newVal && selectedIds.value.length > 0) {
    setTimeout(() => setTableSelection(selectedIds.value), 200);
  }
  // å¯¹è¯æ¡†å…³é—­æ—¶æ¸…空选择状态
  if (!newVal) {
    formalDatabaseSelectedData.value = [];
  }
});
defineExpose({
  Initialization,
  editInitialization,
});
const handleSelectData = (row) => {
  tableData.value = [];
  if (!innerVisible.value) return;
  const selectedData = formalDatabaseSelectedData.value;
  if (selectedData.length === 0) {
    ElMessage.warning("请至少选择一条数据");
    return;
  }
  let addedCount = 0;
  let duplicateCount = 0;
  selectedData.forEach((item) => {
    const newItem = {
      ...item, // å¤åˆ¶æ‰€æœ‰åŽŸå§‹æ•°æ®
      officialId: item.id, // ä¿å­˜åŽŸå§‹çš„id作为officialId
      usedQuantity: 0, // åˆå§‹ä½¿ç”¨æ•°é‡ä¸º0
      // å¯ä»¥æ ¹æ®éœ€è¦æ·»åŠ å…¶ä»–å­—æ®µ
    };
    tableData.value.push(newItem);
    addedCount++;
  });
  // æ›´æ–°selectedIds,确保包含所有当前tableData中的officialId
  const allOfficialIds = tableData.value
      .map((item) => item.officialId)
      .filter((id) => id);
  selectedIds.value = allOfficialIds;
  // å…³é—­é€‰æ‹©å¯¹è¯æ¡†
  innerVisible.value = false;
  // æ˜¾ç¤ºç»“果消息
  let message = "";
  if (addedCount > 0) {
    message += `成功添加 ${addedCount} æ¡æ•°æ®`;
  }
  if (duplicateCount > 0) {
    message += (message ? "," : "") + `跳过 ${duplicateCount} æ¡é‡å¤æ•°æ®`;
  }
  if (message) {
    ElMessage.success(message);
  } else {
    ElMessage.info("没有新数据被添加");
  }
};
const handleSelectionChange = (selection) => {
  formalDatabaseSelectedData.value = selection;
};
// æäº¤è¡¨å• - ä½¿ç”¨å·¥å…·å‡½æ•°éªŒè¯
const handleSubmit = async () => {
  // éªŒè¯ç”Ÿäº§æ˜Žç»†æ•°æ®
  const detailsValidation = validateFormData(detailsTableData.value, [
    "process",
    "unit",
    "schedulingNum",
    "workHours",
    "schedulingDate",
    "schedulingUserId"
  ]);
  if (!detailsValidation.isValid) {
    ElMessage.warning(detailsValidation.message);
    return;
  }
  let num = 0;
  detailsTableData.value.forEach((row) => {
    num += row.schedulingNum
  })
  if(productionQuantity.value <  num){
    ElMessage.warning("待排产数量不能小于生产明细数量")
  }
  console.log(copyForm.value)
  detailsTableData.value.forEach((row) => {
    row.type = copyForm.value.type
    row.productionId = copyForm.value.id
    row.coalId = copyForm.value.coalId
  })
  detailsTableData.value[0].productionQuantity = copyForm.value.productionQuantity
  try{
    const res = await addProductionScheduling(detailsTableData.value)
    if (res.code === 200) {
      dialogVisible.value = false;
      emit("success");
    } else {
      ElMessage.error("提交失败");
    }
  }catch (error){
    ElMessage.error("提交失败,请重试");
  }
};
// å…³é—­å¼¹çª—
const handleClose = () => {
  dialogVisible.value = false;
};
// ä½¿ç”¨æ•°é‡éªŒè¯ - ä½¿ç”¨å·¥å…·å‡½æ•°
const handleCellEdit = (row, prop, value) => {
  if (prop === "usedQuantity") {
    const validation = validateNumber(value, 0, Number(row.inventoryQuantity));
    if (!validation.isValid) {
      ElMessage.warning(validation.message);
      row.usedQuantity = validation.value;
      return;
    }
    row.usedQuantity = validation.value;
  }
};
// å¤„理生产明细表格的操作 - ä½¿ç”¨å·¥å…·å‡½æ•°
const addNewRow = () => {
  const newRow = createDefaultProductionRow(userInfo);
  detailsTableData.value.push(newRow);
};
// é‡ç½®æ•°æ® - ä½¿ç”¨æ·±æ‹·è´
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) => {
};
const handleDeleteRow = (index) => {
  ElMessage.success(`已删除第 ${index + 1} è¡Œæ•°æ®`);
};
// åˆ é™¤å•个已选数据项
const handleRemoveItem = (row) => {
  const index = tableData.value.findIndex(
      (item) => item.officialId === row.officialId
  );
  if (index > -1) {
    tableData.value.splice(index, 1);
    // æ›´æ–°selectedIds
    const updatedOfficialIds = tableData.value
        .map((item) => item.officialId)
        .filter((id) => id);
    selectedIds.value = updatedOfficialIds;
    ElMessage.success("已删除选中项");
  }
};
// è®¡ç®—总使用量
const totalUsedQuantity = computed(() => {
  return tableData.value.reduce((total, item) => {
    const usedQty = Number(item.usedQuantity) || 0;
    return total + usedQty;
  }, 0);
});
</script>
<style scoped lang="scss">
.el-form {
  .el-row {
    padding-top: 20px;
    background: rgba($color: #f8fafb, $alpha: 0.5);
  }
}
.el-row > .el-col > h1 {
  font-weight: bolder;
}
.empty-table > .el-row {
  margin-bottom: 12px;
}
</style>
src/views/production/operationScheduling/components/useCoalData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
/**
 * ç…¤ç§æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›ç…¤ç§æ•°æ®çš„获取、缓存、转换等功能
 */
import {ref, computed, watch} from 'vue';
import {getCoalInfoList} from "@/api/production";
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 getCoalInfoList();
            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.coal : id;
    };
    // æ ¹æ®åç§°èŽ·å–ç…¤ç§ID
    const getCoalIdByName = (name) => {
        if (!name || coalData.value.length === 0) return '';
        const coal = coalData.value.find(item => item.coal === name);
        return coal ? coal.id : '';
    };
    // ç”Ÿæˆä¸‹æ‹‰é€‰é¡¹
    const coalOptions = computed(() => {
        return coalData.value.map(item => ({
            label: item.coal,
            value: item.coal,
            key: item.id
        }));
    });
    // ç”Ÿæˆkey-value映射
    const coalMap = computed(() => {
        const map = {};
        coalData.value.forEach(item => {
            map[item.id] = item.coal;
        });
        return map;
    });
    return {
        coalData: computed(() => coalData.value),
        coalOptions,
        coalMap,
        isLoading: computed(() => isLoading.value),
        isLoaded: computed(() => isLoaded.value),
        getCoalData,
        getCoalNameById,
        getCoalIdByName
    };
}
src/views/production/operationScheduling/components/useDialog.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
/**
 * å¯¹è¯æ¡†ç®¡ç†ç»„合式函数
 * æä¾›å¯¹è¯æ¡†çš„æ‰“开、关闭、数据处理等功能
 */
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' || type === 'viewRow' || type === 'scheduling') && rowData) {
                dialogRef.value.editInitialization?.(type,rowData);
            }
        }
    };
    const viewRow = (type,rowData) => {
        dialogType.value = type;
        currentRowData.value = rowData;
        dialogVisible.value = true;
        openDialog('viewRow', 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,
        viewRow
    };
}
src/views/production/operationScheduling/components/useTableData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
/**
 * è¡¨æ ¼æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›åˆ†é¡µã€æœç´¢ã€é€‰æ‹©ç­‰é€šç”¨åŠŸèƒ½
 */
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,
            };
            console.log('查询参数:', params);
            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] = '';
        console.log('重置搜索参数:', queryParams);
        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/operationScheduling/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,250 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item label="搜索">
        <el-input
            v-model="queryParams.searchAll"
            placeholder="请输入关键词"
            clearable
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSearch">查询</el-button>
        <el-button @click="handleReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- ä¸»è¦å†…容区域 -->
    <el-card>
      <!-- æ•°æ®è¡¨æ ¼ -->
      <ETable
          :showOverflowTooltip="false"
          :loading="loading"
          :table-data="tableData"
          :columns="columns"
          :current-page="queryParams.current"
          :page-size="queryParams.size"
          @selection-change="handleSelectionChange"
          @edit="(row) => openDialog('scheduling', row)"
          :show-selection="true"
          :border="true"
          :operations="['scheduling']"
          :operationsWidth="200"
          :show-overflow-tooltip="false"
          style="width: 100%; height: calc(100vh - 26em)"
      >
        <template #coalId="{ row }">
          <div class="coal-tags">
            <template v-if="row.coalId">
              <el-tag
                  v-for="coal in parseCoalArray(row.coalId)"
                  :key="coal"
                  size="small"
                  type="primary"
                  class="coal-tag"
              >
                {{ getDisplayCoalName(coal) }}
              </el-tag>
            </template>
            <span v-else class="no-data">--</span>
          </div>
        </template>
      </ETable>
      <!-- åˆ†é¡µç»„ä»¶ -->
      <Pagination
          :layout="'total, prev, pager, next, jumper'"
          :total="total"
          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 { onMounted, ref } 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 { delPM,getProductionList } 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";
import { getCoalInfoList } from "@/api/production";
// ç…¤ç§ä¿¡æ¯åˆ—表
const coalInfoList = ref([]);
// è¡¨æ ¼åˆ—配置
const columns = [
  { prop: "coalId", label: "煤种", minWidth: 150, slot: true },
  {
    prop: "status",
    label: "状态",
    minWidth: 150,
    formatter: (row) => {
      const statusMap = {
        1: '待排产',
        2: '排产中',
        3: '已排产'
      };
      return statusMap[row.status] || '未知状态';
    }
  },
  { prop: "producer", label: "生产人", minWidth: 150 },
  { prop: "productionQuantity", label: "生产数量", minWidth: 120 },
  { prop: "laborCost", label: "人工成本", minWidth: 150 },
  { prop: "energyConsumptionCost", label: "能耗成本", minWidth: 120 },
  { prop: "equipmentDepreciation", label: "设备折旧", minWidth: 143 },
  { prop: "totalCost", label: "总成本", minWidth: 150 },
];
// ä½¿ç”¨è¡¨æ ¼æ•°æ®ç»„合式函数
const {
  tableData,
  loading,
  total,
  selectedRows,
  queryParams,
  getList,
  handleSearch,
  handleReset,
  handlePageChange,
  handleSelectionChange,
  deleteSelected,
} = useTableData(getProductionList, { pageSize: 10 });
// ä½¿ç”¨å¯¹è¯æ¡†ç»„合式函数
const {
  dialogVisible,
  dialogType,
  dialogRef,
  openDialog,
  handleDialogSuccess: onDialogSuccess,
} = useDialog();
// ä½¿ç”¨ç…¤ç§æ•°æ®ç»„合式函数
const { getCoalNameById, getCoalData } = useCoalData();
// èŽ·å–ç…¤ç§æ˜¾ç¤ºåç§°ï¼ˆå¸¦å¤‡ç”¨é€»è¾‘ï¼‰
const getDisplayCoalName = (coalId) => {
  // ä¼˜å…ˆä½¿ç”¨ useCoalData çš„æ–¹æ³•
  let name = getCoalNameById(coalId);
  // å¦‚果没有找到,尝试从 coalInfoList ä¸­æŸ¥æ‰¾
  if (name === coalId && coalInfoList.value.length > 0) {
    const found = coalInfoList.value.find((item) => item.id == coalId);
    name = found ? found.coal : coalId;
  }
  return name || coalId;
};
// å¤„理生产数据更新
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 = () => {
  onDialogSuccess(() => {
    getList();
    ElMessage.success("操作成功");
  });
};
// ç»„件挂载时加载数据
onMounted(async () => {
  try {
    // å¹¶è¡ŒåŠ è½½ç…¤ç§æ•°æ®å’Œè¡¨æ ¼æ•°æ®
    await Promise.all([
      getCoalData(), // é¢„加载煤种数据
      (async () => {
        const res = await getCoalInfoList();
        if (res.code === 200) {
          coalInfoList.value = res.data;
        }
      })(),
    ]);
    // åŠ è½½è¡¨æ ¼æ•°æ®
    getList();
  } catch (error) {
    ElMessage.error("数据加载失败,请刷新页面重试");
  }
});
</script>
<style scoped lang="scss">
.production-container {
  padding: 20px;
  .el-card:nth-child(1) {
    margin-bottom: 20px;
  }
}
.search-bar {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  .el-input {
    width: 20%;
  }
}
.search-form {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-bottom: 20px;
  .el-form-item {
    margin-right: 10px;
  }
  .el-button {
    margin-left: 10px;
  }
}
.coal-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  .coal-tag {
    margin-right: 4px;
    margin-bottom: 4px;
    &:last-child {
      margin-right: 0;
    }
  }
  .no-data {
    color: #999;
    font-style: italic;
  }
}
</style>
src/views/production/productionReporting/components/ProductionDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,348 @@
<template>
  <el-dialog
      v-model="dialogVisible"
      title="生产报工"
      width="70%"
      @close="handleClose"
  >
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="排产数量:">
            <el-input v-model="productionQuantity" placeholder="请输入" clearable disabled/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="本次生产数量:" prop="finishedNum">
            <el-input-number
                v-model="form.finishedNum"
                placeholder="请输入"
                :min="0"
                :step="0.1"
                :precision="2"
                clearable
                style="width: 100%"
                @change="changeNum"
            />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="待生产数量:">
            <el-input v-model="pendingNum" placeholder="请输入" clearable disabled/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="生产人:" prop="schedulingUserId">
            <el-select
                v-model="form.schedulingUserId"
                placeholder="选择人员"
                style="width: 100%;"
            >
              <el-option
                  v-for="user in userList"
                  :key="user.userId"
                  :label="user.nickName"
                  :value="user.userId"
              />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleSubmit">确认</el-button>
        <el-button @click="handleClose">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import {ref, reactive, watch, onMounted, nextTick, computed} from "vue";
import ETable from "@/components/Table/ETable.vue";
import ETableModify from "@/components/Table/EtableModify.vue";
import {ElMessage, ElMessageBox, ElAlert, ElText} from "element-plus";
import {Delete, Warning, Plus} from "@element-plus/icons-vue";
import {validateFormData, validateNumber, deepClone, createDefaultProductionRow} from "@/utils/production";
import {useCoalData} from "./useCoalData";
import useUserStore from "@/store/modules/user";
import {work} from '@/api/productionScheduling/index'
import {userListAll} from "@/api/publicApi";
const userList = ref([])
const data = reactive({
  form: {
    successNum: 0,
    schedulingNum: 0,
    finishedNum: 0,
    schedulingUserId: ""
  },
  rules: {
    schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
  },
});
const { form, rules } = toRefs(data);
const changeNum = (value) => {
  if (value > pendingNum.value) {
    form.value.finishedNum = pendingNum.value
    ElMessage.warning("本次生产数量不可大于排产数量");
  }
  pendingNum.value = pendingNum.value - form.value.finishedNum;
}
// Props å’Œ Emits
const props = defineProps({
  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", "update:productionAndProcessing"]);
// ç”¨æˆ·ä¿¡æ¯å’Œç…¤ç§æ•°æ®
const userStore = useUserStore();
const {getCoalNameById} = useCoalData();
let userInfo;
// å¯¹è¯æ¡†çŠ¶æ€
const innerVisible = ref(false);
const dialogType = ref("add");
const loading = ref(false);
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 productionQuantity = ref(0);
const pendingNum = ref(0);
const handleRowClick = (row) => {
  currentRow.value = row;
};
// æ‰‹åŠ¨è®¾ç½®è¡¨æ ¼é€‰ä¸­çŠ¶æ€
const setTableSelection = (ids) => {
  if (!etableRef.value || !Array.isArray(ids) || ids.length === 0) {
    return;
  }
  nextTick(() => {
    setTimeout(() => {
      try {
        etableRef.value.clearSelection();
        const rowsToSelect = formalDatabaseData.value.filter((row) =>
            ids.includes(row.id)
        );
        if (rowsToSelect.length > 0) {
          etableRef.value.setRowsSelection(rowsToSelect, true);
        }
      } catch (error) {
      }
    }, 150);
  });
};
// åˆå§‹åŒ–和编辑初始化
const Initialization = async () => {
  tableData.value = [];
  form.value = {
    successNum: 0,
    schedulingNum: 0,
    finishedNum: 0,
    schedulingUserId: ""
  };
  detailsTableData.value = [];
  copyForm.value = null;
  dialogType.value = "add";
};
const editInitialization = async (type, data) => {
  //清空form
  Initialization();
  productionQuantity.value = data.schedulingNum;
  pendingNum.value = data.schedulingNum - data.successNum;
  copyForm.value = deepClone(data);
  tableData.value = data.productionInventoryList || [];
  detailsTableData.value = data.productionList || [];
  dialogType.value = type;
  const existingOfficialIds = tableData.value
      .map((item) => item.officialId)
      .filter((id) => id);
  selectedIds.value = existingOfficialIds;
};
// ç›‘听对话框状态,在打开时设置选中状态
watch(innerVisible, (newVal) => {
  if (newVal && selectedIds.value.length > 0) {
    setTimeout(() => setTableSelection(selectedIds.value), 200);
  }
  // å¯¹è¯æ¡†å…³é—­æ—¶æ¸…空选择状态
  if (!newVal) {
    formalDatabaseSelectedData.value = [];
  }
});
defineExpose({
  Initialization,
  editInitialization,
});
const handleSelectData = (row) => {
  tableData.value = [];
  if (!innerVisible.value) return;
  const selectedData = formalDatabaseSelectedData.value;
  if (selectedData.length === 0) {
    ElMessage.warning("请至少选择一条数据");
    return;
  }
  let addedCount = 0;
  let duplicateCount = 0;
  selectedData.forEach((item) => {
    const newItem = {
      ...item, // å¤åˆ¶æ‰€æœ‰åŽŸå§‹æ•°æ®
      officialId: item.id, // ä¿å­˜åŽŸå§‹çš„id作为officialId
      usedQuantity: 0, // åˆå§‹ä½¿ç”¨æ•°é‡ä¸º0
      // å¯ä»¥æ ¹æ®éœ€è¦æ·»åŠ å…¶ä»–å­—æ®µ
    };
    tableData.value.push(newItem);
    addedCount++;
  });
  // æ›´æ–°selectedIds,确保包含所有当前tableData中的officialId
  const allOfficialIds = tableData.value
      .map((item) => item.officialId)
      .filter((id) => id);
  selectedIds.value = allOfficialIds;
  // å…³é—­é€‰æ‹©å¯¹è¯æ¡†
  innerVisible.value = false;
  // æ˜¾ç¤ºç»“果消息
  let message = "";
  if (addedCount > 0) {
    message += `成功添加 ${addedCount} æ¡æ•°æ®`;
  }
  if (duplicateCount > 0) {
    message += (message ? "," : "") + `跳过 ${duplicateCount} æ¡é‡å¤æ•°æ®`;
  }
  if (message) {
    ElMessage.success(message);
  } else {
    ElMessage.info("没有新数据被添加");
  }
};
const handleSelectionChange = (selection) => {
  formalDatabaseSelectedData.value = selection;
};
// æäº¤è¡¨å• - ä½¿ç”¨å·¥å…·å‡½æ•°éªŒè¯
const handleSubmit = async () => {
  console.log(copyForm.value)
  try{
    const res = await work({id: copyForm.value.id,successNum: form.value.finishedNum,schedulingUserId: form.value.schedulingUserId})
    if (res.code === 200) {
      dialogVisible.value = false;
      emit("success");
    } else {
      ElMessage.error("提交失败");
    }
  }catch (error){
    ElMessage.error("提交失败,请重试");
  }
};
// å…³é—­å¼¹çª—
const handleClose = () => {
  dialogVisible.value = false;
};
// ä½¿ç”¨æ•°é‡éªŒè¯ - ä½¿ç”¨å·¥å…·å‡½æ•°
const handleCellEdit = (row, prop, value) => {
  if (prop === "usedQuantity") {
    const validation = validateNumber(value, 0, Number(row.inventoryQuantity));
    if (!validation.isValid) {
      ElMessage.warning(validation.message);
      row.usedQuantity = validation.value;
      return;
    }
    row.usedQuantity = validation.value;
  }
};
// å¤„理生产明细表格的操作 - ä½¿ç”¨å·¥å…·å‡½æ•°
const addNewRow = () => {
  const newRow = createDefaultProductionRow(userInfo);
  detailsTableData.value.push(newRow);
};
// èŽ·å–ç”¨æˆ·ä¿¡æ¯å¹¶åŠ è½½åŸºç¡€æ•°æ®
onMounted(async () => {
  try {
    let ress = await userListAll();
    userList.value = ress.data;
    userInfo = await userStore.getInfo();
  } catch (error) {
    ElMessage.error("初始化失败,请重试");
  }
});
// ç®€åŒ–的事件处理函数
const handleDetailsChange = (data) => {
};
const handleDeleteRow = (index) => {
  ElMessage.success(`已删除第 ${index + 1} è¡Œæ•°æ®`);
};
// åˆ é™¤å•个已选数据项
const handleRemoveItem = (row) => {
  const index = tableData.value.findIndex(
      (item) => item.officialId === row.officialId
  );
  if (index > -1) {
    tableData.value.splice(index, 1);
    // æ›´æ–°selectedIds
    const updatedOfficialIds = tableData.value
        .map((item) => item.officialId)
        .filter((id) => id);
    selectedIds.value = updatedOfficialIds;
    ElMessage.success("已删除选中项");
  }
};
// è®¡ç®—总使用量
const totalUsedQuantity = computed(() => {
  return tableData.value.reduce((total, item) => {
    const usedQty = Number(item.usedQuantity) || 0;
    return total + usedQty;
  }, 0);
});
</script>
<style scoped lang="scss">
.el-form {
  .el-row {
    padding-top: 20px;
    background: rgba($color: #f8fafb, $alpha: 0.5);
  }
}
.el-row > .el-col > h1 {
  font-weight: bolder;
}
.empty-table > .el-row {
  margin-bottom: 12px;
}
</style>
src/views/production/productionReporting/components/useCoalData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
/**
 * ç…¤ç§æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›ç…¤ç§æ•°æ®çš„获取、缓存、转换等功能
 */
import {ref, computed, watch} from 'vue';
import {getCoalInfoList} from "@/api/production";
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 getCoalInfoList();
            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.coal : id;
    };
    // æ ¹æ®åç§°èŽ·å–ç…¤ç§ID
    const getCoalIdByName = (name) => {
        if (!name || coalData.value.length === 0) return '';
        const coal = coalData.value.find(item => item.coal === name);
        return coal ? coal.id : '';
    };
    // ç”Ÿæˆä¸‹æ‹‰é€‰é¡¹
    const coalOptions = computed(() => {
        return coalData.value.map(item => ({
            label: item.coal,
            value: item.coal,
            key: item.id
        }));
    });
    // ç”Ÿæˆkey-value映射
    const coalMap = computed(() => {
        const map = {};
        coalData.value.forEach(item => {
            map[item.id] = item.coal;
        });
        return map;
    });
    return {
        coalData: computed(() => coalData.value),
        coalOptions,
        coalMap,
        isLoading: computed(() => isLoading.value),
        isLoaded: computed(() => isLoaded.value),
        getCoalData,
        getCoalNameById,
        getCoalIdByName
    };
}
src/views/production/productionReporting/components/useDialog.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
/**
 * å¯¹è¯æ¡†ç®¡ç†ç»„合式函数
 * æä¾›å¯¹è¯æ¡†çš„æ‰“开、关闭、数据处理等功能
 */
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' || type === 'viewRow' || type === 'scheduling' || type === 'work') && rowData) {
                dialogRef.value.editInitialization?.(type,rowData);
            }
        }
    };
    const viewRow = (type,rowData) => {
        dialogType.value = type;
        currentRowData.value = rowData;
        dialogVisible.value = true;
        openDialog('viewRow', 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,
        viewRow
    };
}
src/views/production/productionReporting/components/useTableData.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
/**
 * è¡¨æ ¼æ•°æ®ç®¡ç†ç»„合式函数
 * æä¾›åˆ†é¡µã€æœç´¢ã€é€‰æ‹©ç­‰é€šç”¨åŠŸèƒ½
 */
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,
            };
            console.log('查询参数:', params);
            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] = '';
        console.log('重置搜索参数:', queryParams);
        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/productionReporting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,277 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item label="搜索">
        <el-input
            v-model="queryParams.searchAll"
            placeholder="请输入关键词"
            clearable
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSearch">查询</el-button>
        <el-button @click="handleReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- ä¸»è¦å†…容区域 -->
    <el-card>
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="toolbar">
        <el-button
            type="danger"
            :icon="Delete"
            :disabled="!selectedRows.length"
            @click="() => deleteSelected(delProductionScheduling)"
        >
          åˆ é™¤
        </el-button>
      </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('work', row)"
          :show-selection="true"
          :border="true"
          :operations="['work']"
          :operationsWidth="200"
          :show-overflow-tooltip="false"
          style="width: 100%; height: calc(100vh - 26em)"
      >
        <template #coalId="{ row }">
          <div class="coal-tags">
            <template v-if="row.coalId">
              <el-tag
                  v-for="coal in parseCoalArray(row.coalId)"
                  :key="coal"
                  size="small"
                  type="primary"
                  class="coal-tag"
              >
                {{ getDisplayCoalName(coal) }}
              </el-tag>
            </template>
            <span v-else class="no-data">--</span>
          </div>
        </template>
      </ETable>
      <!-- åˆ†é¡µç»„ä»¶ -->
      <Pagination
          :layout="'total, prev, pager, next, jumper'"
          :total="total"
          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 { onMounted, ref } 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 { listPage,delProductionScheduling } from "@/api/productionScheduling";
import { parseCoalArray } from "@/utils/production";
import { useTableData } from "./components/useTableData.js";
import { useDialog } from "./components/useDialog.js";
import { useCoalData } from "./components/useCoalData.js";
import { getCoalInfoList } from "@/api/production";
// ç…¤ç§ä¿¡æ¯åˆ—表
const coalInfoList = ref([]);
// è¡¨æ ¼åˆ—配置
const columns = [
  { prop: "coalId", label: "煤种", minWidth: 150, slot: true },
  {
    prop: "type",
    label: "煤料类型",
    minWidth: 150,
    formatter: (row) => {
      const statusMap = {
        1: '成品',
        2: '原料'
      };
      return statusMap[row.type] || '未知类型';
    }
  },
  {
    prop: "status",
    label: "状态",
    minWidth: 150,
    formatter: (row) => {
      const statusMap = {
        1: '待生产',
        2: '生产中',
        3: '已入库'
      };
      return statusMap[row.status] || '未知状态';
    }
  },
  { prop: "schedulingUserName", label: "生产人", minWidth: 150 },
  { prop: "schedulingNum", label: "生产数量", minWidth: 120 },
  { prop: "successNum", label: "入库数量", minWidth: 120,
    formatter: (row) => {
      return row.successNum || '0';
    } },
  { prop: "workHours", label: "工时定额", minWidth: 150 },
  { prop: "unit", label: "单位", minWidth: 120 },
  { prop: "process", label: "工序", minWidth: 143 },
  { prop: "schedulingDate", label: "排产日期", minWidth: 150 },
];
// ä½¿ç”¨è¡¨æ ¼æ•°æ®ç»„合式函数
const {
  tableData,
  loading,
  total,
  selectedRows,
  queryParams,
  getList,
  handleSearch,
  handleReset,
  handlePageChange,
  handleSelectionChange,
  deleteSelected,
} = useTableData(listPage, { pageSize: 10 });
// ä½¿ç”¨å¯¹è¯æ¡†ç»„合式函数
const {
  dialogVisible,
  dialogType,
  dialogRef,
  openDialog,
  handleDialogSuccess: onDialogSuccess,
} = useDialog();
// ä½¿ç”¨ç…¤ç§æ•°æ®ç»„合式函数
const { getCoalNameById, getCoalData } = useCoalData();
// èŽ·å–ç…¤ç§æ˜¾ç¤ºåç§°ï¼ˆå¸¦å¤‡ç”¨é€»è¾‘ï¼‰
const getDisplayCoalName = (coalId) => {
  // ä¼˜å…ˆä½¿ç”¨ useCoalData çš„æ–¹æ³•
  let name = getCoalNameById(coalId);
  // å¦‚果没有找到,尝试从 coalInfoList ä¸­æŸ¥æ‰¾
  if (name === coalId && coalInfoList.value.length > 0) {
    const found = coalInfoList.value.find((item) => item.id == coalId);
    name = found ? found.coal : coalId;
  }
  return name || coalId;
};
// å¤„理生产数据更新
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 = () => {
  onDialogSuccess(() => {
    getList();
    ElMessage.success("操作成功");
  });
};
// ç»„件挂载时加载数据
onMounted(async () => {
  try {
    // å¹¶è¡ŒåŠ è½½ç…¤ç§æ•°æ®å’Œè¡¨æ ¼æ•°æ®
    await Promise.all([
      getCoalData(), // é¢„加载煤种数据
      (async () => {
        const res = await getCoalInfoList();
        if (res.code === 200) {
          coalInfoList.value = res.data;
        }
      })(),
    ]);
    // åŠ è½½è¡¨æ ¼æ•°æ®
    getList();
  } catch (error) {
    ElMessage.error("数据加载失败,请刷新页面重试");
  }
});
</script>
<style scoped lang="scss">
.production-container {
  padding: 20px;
  .el-card:nth-child(1) {
    margin-bottom: 20px;
  }
}
.search-bar {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  .el-input {
    width: 20%;
  }
}
.search-form {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-bottom: 20px;
  .el-form-item {
    margin-right: 10px;
  }
  .el-button {
    margin-left: 10px;
  }
}
.coal-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  .coal-tag {
    margin-right: 4px;
    margin-bottom: 4px;
    &:last-child {
      margin-right: 0;
    }
  }
  .no-data {
    color: #999;
    font-style: italic;
  }
}
</style>
src/views/warehouseManagement/index.vue
@@ -82,6 +82,11 @@
            width="180"
            sortable
          />
          <el-table-column prop="type" label="煤料类型">
            <template #default="scope">
              {{scope.row.type === 1 ? '成品' : '原料'}}
            </template>
          </el-table-column>
          <el-table-column prop="coal" label="煤种" sortable />
          <el-table-column prop="unit" label="单位" width="70" />
          <el-table-column
@@ -440,6 +445,22 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="煤料类型" prop="type">
              <el-select
                  v-model="mergeForm.type"
                  placeholder="请选择煤料类型"
                  :disabled="operationType === 'view'"
              >
                <el-option
                    :label="item.label"
                    v-for="item in typeList"
                    :key="item.value"
                    :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-divider></el-divider>
        <el-row>
@@ -499,6 +520,16 @@
// åˆå¹¶å¼¹æ¡†
const mergeVisible = ref(false);
const operationType = ref("");
const typeList = ref([
    {
      label: "成品",
      value: 1,
    },
    {
      label: "原料",
      value: 2,
    },
]);
const data = reactive({
  form: {
    supplierName: "",