gaoluyang
5 小时以前 bfff831304d65d948613e3774364bb29bebaeb0c
src/views/procurementManagement/procurementPlan/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,856 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card"
             shadow="never">
      <el-form :model="searchForm"
               :inline="true"
               class="search-form">
        <el-form-item label="计划名称">
          <el-input v-model="searchForm.planName"
                    placeholder="请输入计划名称"
                    clearable />
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchForm.status"
                     placeholder="请选择状态"
                     clearable
                     style="width: 150px">
            <el-option label="启用"
                       value="active" />
            <el-option label="禁用"
                       value="disabled" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleSearch">
            <el-icon>
              <Search />
            </el-icon>
            æœç´¢
          </el-button>
          <el-button @click="handleReset">
            <el-icon>
              <Refresh />
            </el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- æ“ä½œæŒ‰é’® -->
    <el-card class="table-card"
             shadow="never">
      <div class="table-header">
        <div class="table-title">采购计划列表</div>
        <div class="table-actions">
          <el-button type="primary"
                     @click="handleAdd">
            <el-icon>
              <Plus />
            </el-icon>
            æ–°å¢žè®¡åˆ’
          </el-button>
          <el-button type="info"
                     @click="handleExport">
            <el-icon>
              <Download />
            </el-icon>
            å¯¼å‡º
          </el-button>
        </div>
      </div>
      <!-- æ•°æ®è¡¨æ ¼ -->
      <el-table v-loading="loading"
                :data="tableData"
                stripe
                border
                style="width: 100%">
        <el-table-column prop="planName"
                         label="计划名称"
                         min-width="150" />
        <el-table-column prop="description"
                         label="描述"
                         min-width="200"
                         show-overflow-tooltip />
        <el-table-column prop="formula"
                         label="计算公式"
                         min-width="200"
                         show-overflow-tooltip>
          <template #default="{ row }">
            <el-tag type="info"
                    size="small">{{ row.formula }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="status"
                         label="状态"
                         width="80"
                         align="center">
          <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'info'"
                    size="small">
              {{ row.status === 'active' ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="updateTime"
                         label="最后计算时间"
                         width="160" />
        <el-table-column label="操作"
                         width="200"
                         fixed="right"
                         align="center">
          <template #default="{ row }">
            <el-button type="primary"
                       link
                       @click="handleEdit(row)">编辑</el-button>
            <el-button type="success"
                       link
                       @click="handleCalculate(row)">计算</el-button>
            <el-button type="danger"
                       link
                       @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <div class="pagination-container">
        <el-pagination v-model:current-page="pagination.current"
                       v-model:page-size="pagination.size"
                       :page-sizes="[10, 20, 50, 100]"
                       :total="total"
                       layout="total, sizes, prev, pager, next, jumper"
                       @size-change="handleSizeChange"
                       @current-change="handleCurrentChange" />
      </div>
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <FormDialog v-model="dialogVisible"
                :title="dialogType === 'add' ? '新增采购计划' : '编辑采购计划'"
                :width="'1000px'"
                :operation-type="dialogType"
                :close-on-click-modal="false"
                @close="dialogVisible = false"
                @confirm="handleSubmit"
                @cancel="dialogVisible = false">
      <div class="form-container">
        <!-- åŸºæœ¬ä¿¡æ¯ -->
        <div class="form-section">
          <div class="section-title">基本信息</div>
          <el-form ref="formRef"
                   :model="formData"
                   :rules="formRules"
                   label-width="120px">
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="编码"
                              prop="code">
                  <el-input v-model="formData.code"
                            placeholder="保存后自动生成"
                            disabled />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="名称"
                              prop="planName"
                              required>
                  <el-input v-model="formData.planName"
                            placeholder="请输入计划名称" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-form-item label="描述"
                          prop="description">
              <el-input v-model="formData.description"
                        type="textarea"
                        :rows="3"
                        placeholder="请输入计划描述" />
            </el-form-item>
            <el-row :gutter="20">
              <el-col :span="12">
                <el-form-item label="状态"
                              prop="status">
                  <el-select v-model="formData.status"
                             placeholder="请选择状态"
                             style="width: 100%">
                    <el-option label="启用"
                               value="active" />
                    <el-option label="禁用"
                               value="disabled" />
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="创建时间" prop="createTime">
                  <el-date-picker v-model="formCreateTimeDate"
                                  type="date"
                                  placeholder="选择日期"
                                  value-format="YYYY-MM-DD"
                                  style="width: 100%" />
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
        <!-- è®¡ç®—参数 -->
        <div class="form-section">
          <div class="section-title">计算参数</div>
          <el-tabs v-model="activeTab"
                   class="param-tabs">
            <el-tab-pane label="需求参数"
                         name="demand">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
                <el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
                <el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
                <el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
                <el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
                <el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
              </div>
            </el-tab-pane>
            <el-tab-pane label="计算参数"
                         name="calculation">
              <div class="checkbox-group">
                <el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
                <el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
                <el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
                <el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
                <el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
                <el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
                <el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
              </div>
            </el-tab-pane>
          </el-tabs>
        </div>
        <!-- æ±‡æ€»åˆå¹¶é€‰é¡¹ -->
        <div class="form-section">
          <div class="section-title">汇总合并选项</div>
          <div class="checkbox-group">
            <el-checkbox v-model="formData.summaryMaterial">物料</el-checkbox>
            <el-checkbox v-model="formData.summaryAuxAttributes">辅助属性</el-checkbox>
            <el-checkbox v-model="formData.summaryDemandDate">需求日期</el-checkbox>
          </div>
        </div>
        <!-- è®¡ç®—公式 -->
        <div class="form-section">
          <div class="section-title">计算公式</div>
          <div class="formula-input-section">
            <el-form-item label="计算公式"
                          prop="formula"
                          required>
              <el-input v-model="formData.formula"
                        placeholder="例如: é¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量"
                        @input="validateFormula" />
            </el-form-item>
            <div class="formula-help">
              <el-text type="info"
                       size="small">
                æ”¯æŒå˜é‡ï¼šé¢„计出库数量、现有库存、安全库存、预计入库数量
              </el-text>
            </div>
          </div>
        </div>
      </div>
    </FormDialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <FormDialog v-model="productSelectDialogVisible"
                title="选择产品"
                :width="'800px'"
                :close-on-click-modal="false"
                @close="productSelectDialogVisible = false"
                @confirm="handleConfirmProductSelection"
                @cancel="productSelectDialogVisible = false">
      <div class="product-select">
        <el-alert title="请选择要计算的产品"
                  type="info"
                  :closable="false"
                  show-icon>
          <template #default>
            <p>选择产品后,系统将根据当前计算公式和产品库存情况进行计算。</p>
          </template>
        </el-alert>
        <el-table v-loading="productLoading"
                  :data="productList"
                  @selection-change="handleProductSelectionChange"
                  stripe
                  border
                  style="width: 100%; margin-top: 20px;">
          <el-table-column type="selection"
                           width="55" />
          <el-table-column prop="productCategory"
                           label="产品大类"
                           min-width="150" />
          <el-table-column prop="specificationModel"
                           label="规格型号"
                           width="120" />
          <el-table-column prop="inboundNum0"
                           label="现有库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="安全库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="预计出库"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum0"
                           label="预计入库"
                           width="100"
                           align="right" />
        </el-table>
      </div>
    </FormDialog>
    <!-- è®¡ç®—结果对话框 -->
    <FormDialog v-model="calculateDialogVisible"
                title="采购计算结果"
                :width="'1000px'"
                :close-on-click-modal="false"
                @close="calculateDialogVisible = false"
                @confirm="handleCreatePurchaseOrder"
                @cancel="calculateDialogVisible = false">
      <div class="calculate-result">
        <el-alert title="计算结果"
                  type="success"
                  :closable="false"
                  show-icon>
          <template #default>
            <p>基于当前配置的计算公式和库存情况,系统已计算出各产品的采购需求。</p>
          </template>
        </el-alert>
        <el-table :data="calculateResult"
                  stripe
                  border
                  style="width: 100%; margin-top: 20px;">
          <el-table-column prop="productCategory"
                           label="产品大类"
                           min-width="150" />
          <el-table-column prop="specificationModel"
                           label="规格型号"
                           width="120" />
          <el-table-column prop="inboundNum0"
                           label="现有库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="安全库存"
                           width="100"
                           align="right" />
          <el-table-column prop="inboundNum"
                           label="预计出库数量"
                           width="120"
                           align="right" />
          <el-table-column prop="inboundNum0"
                           label="预计入库数量"
                           width="120"
                           align="right" />
          <el-table-column prop="weeklyNetDemand"
                           label="按周净需求"
                           width="120"
                           align="right">
            <template #default="{ row }">
              <el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'"
                      size="small">
                {{ row.weeklyNetDemand }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="suggestedPurchase"
                           label="建议采购"
                           width="100"
                           align="right">
            <template #default="{ row }">
              <el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'"
                      size="small">
                {{ row.suggestedPurchase }}
              </el-tag>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </FormDialog>
  </div>
</template>
<script setup>
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import { ref, reactive, onMounted, getCurrentInstance, computed } from "vue";
  import dayjs from "dayjs";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Search, Refresh, Plus, Download } from "@element-plus/icons-vue";
  import {
    listPage,
    add,
    update,
    del,
    listPageCopy,
  } from "@/api/procurementManagement/procurementPlan.js";
  // å“åº”式数据
  const loading = ref(false);
  const submitLoading = ref(false);
  const dialogVisible = ref(false);
  const productSelectDialogVisible = ref(false);
  const calculateDialogVisible = ref(false);
  const dialogType = ref("add");
  const productLoading = ref(false);
  const selectedProducts = ref([]);
  const currentPlan = ref(null);
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    planName: "",
    status: "",
  });
  // åˆ†é¡µæ•°æ®
  const pagination = reactive({
    current: 1,
    size: 20,
  });
  // è¡¨å•数据
  const formData = reactive({
    code: "",
    planName: "",
    description: "",
    status: "",
    isSystemPreset: false,
    formula: "",
    createTime: "",
    // è®¡ç®—参数
    considerExistingStock: false,
    warehouseControl: false,
    calculateTotalDemand: false,
    considerSafetyStock: false,
    considerLockedStock: false,
    notConsiderMaterialAux: false,
    negativeStockAsDemand: false,
    // æ±‡æ€»åˆå¹¶é€‰é¡¹
    summaryMaterial: false,
    summaryAuxAttributes: false,
    summaryDemandDate: false,
  });
  const formCreateTimeDate = computed({
    get: () => (formData.createTime ? String(formData.createTime).split(" ")[0] : ""),
    set: (value) => {
      formData.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : "";
    },
  });
  // å½“前激活的标签页
  const activeTab = ref("demand");
  // è¡¨å•验证规则
  const formRules = {
    planName: [{ required: true, message: "请输入计划名称", trigger: "blur" }],
    status: [{ required: true, message: "请选择状态", trigger: "change" }],
    formula: [{ required: true, message: "请输入计算公式", trigger: "blur" }],
  };
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  // äº§å“åˆ—表数据
  const productList = ref([
    {
      id: 4,
      productName: "产品D",
      productCode: "PD004",
      existingStock: 90,
      safetyStock: 40,
      expectedOutbound: 160,
      expectedInbound: 35,
    },
  ]);
  // è®¡ç®—结果数据
  const calculateResult = ref([
    {
      productName: "产品A",
      existingStock: 100,
      safetyStock: 50,
      expectedOutbound: 200,
      expectedInbound: 30,
      weeklyNetDemand: 120,
      suggestedPurchase: 150,
    },
    {
      productName: "产品B",
      existingStock: 80,
      safetyStock: 30,
      expectedOutbound: 150,
      expectedInbound: 20,
      weeklyNetDemand: 100,
      suggestedPurchase: 120,
    },
  ]);
  const total = ref(0);
  // æ–¹æ³•
  const handleSearch = () => {
    pagination.current = 1;
    loadData();
  };
  const handleReset = () => {
    Object.assign(searchForm, {
      planName: "",
      status: "",
    });
    handleSearch();
  };
  const loadData = () => {
    loading.value = true;
    listPage({ ...searchForm, ...pagination }).then(res => {
      if (res.code === 200) {
        tableData.value = res.data.records;
        total.value = res.data.total;
        loading.value = false;
      }
    });
  };
  const handleAdd = () => {
    dialogType.value = "add";
    resetForm();
    formData.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    dialogVisible.value = true;
  };
  const handleEdit = row => {
    dialogType.value = "edit";
    Object.assign(formData, row);
    dialogVisible.value = true;
  };
  const handleDelete = async row => {
    try {
      await ElMessageBox.confirm("确定要删除这个采购计划吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      });
      let ids = [row.id];
      del(ids).then(res => {
        if (res.code === 200) {
          ElMessage.success("删除成功");
          loadData();
        }
      });
    } catch {
      // ç”¨æˆ·å–消删除
    }
  };
  const handleSubmit = async () => {
    try {
      // è¡¨å•验证
      if (!formData.planName || !formData.formula) {
        ElMessage.error("请填写必填项");
        return;
      }
      submitLoading.value = true;
      if (dialogType.value === "add") {
        add(formData).then(res => {
          if (res.code === 200) {
            ElMessage.success("新增成功");
            dialogVisible.value = false;
            loadData();
          }
        });
      } else {
        // ç¼–辑
        update(formData).then(res => {
          if (res.code === 200) {
            ElMessage.success("编辑成功");
            dialogVisible.value = false;
            loadData();
          }
        });
      }
    } catch (error) {
      ElMessage.error("操作失败");
    } finally {
      submitLoading.value = false;
    }
  };
  const resetForm = () => {
    Object.assign(formData, {
      code: "",
      planName: "",
      description: "",
      status: "",
      isSystemPreset: false,
      formula: "预计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量",
      // è®¡ç®—参数
      considerExistingStock: false,
      warehouseControl: false,
      calculateTotalDemand: false,
      considerSafetyStock: false,
      considerLockedStock: false,
      notConsiderMaterialAux: false,
      negativeStockAsDemand: false,
      // æ±‡æ€»åˆå¹¶é€‰é¡¹
      summaryMaterial: false,
      summaryAuxAttributes: false,
      summaryDemandDate: false,
    });
    activeTab.value = "demand";
  };
  const validateFormula = () => {
    // ç®€å•的公式验证
    const formula = formData.formula;
    if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
      ElMessage.warning("公式格式可能不正确,请检查");
    }
  };
  const handleCalculate = row => {
    currentPlan.value = row;
    productSelectDialogVisible.value = true;
    loadProductList();
  };
  const loadProductList = () => {
    productLoading.value = true;
    // æ¨¡æ‹ŸåŠ è½½äº§å“æ•°æ®
    listPageCopy({ size: -1 }).then(res => {
      if (res.code === 200) {
        productList.value = res.data.records;
        productLoading.value = false;
      }
    });
  };
  const handleProductSelectionChange = selection => {
    selectedProducts.value = selection;
  };
  const handleConfirmProductSelection = () => {
    if (selectedProducts.value.length === 0) {
      ElMessage.warning("请选择要计算的产品");
      return;
    }
    ElMessage.success(`正在计算 ${currentPlan.value.planName} çš„采购需求...`);
    productSelectDialogVisible.value = false;
    // æ ¹æ®é€‰æ‹©çš„产品和计算公式进行计算
    calculateWithSelectedProducts();
  };
  const calculateWithSelectedProducts = () => {
    // æ¨¡æ‹Ÿè®¡ç®—过程
    // æ ¹æ®é€‰æ‹©çš„产品更新计算结果
    const result = selectedProducts.value.map(product => {
      // è¿™é‡Œåº”该根据实际的计算公式进行计算
      // ç¤ºä¾‹ï¼šé¢„计出库数量 - çŽ°æœ‰åº“å­˜ + å®‰å…¨åº“å­˜ - é¢„计入库数量
      const weeklyNetDemand =
        product.inboundNum -
        product.inboundNum0 +
        product.inboundNum -
        product.inboundNum0;
      const suggestedPurchase = Math.max(0, weeklyNetDemand);
      return {
        productCategory: product.productCategory,
        specificationModel: product.specificationModel,
        inboundNum0: product.inboundNum0,
        inboundNum: product.inboundNum,
        weeklyNetDemand: weeklyNetDemand,
        suggestedPurchase: suggestedPurchase,
      };
    });
    calculateResult.value = result;
    calculateDialogVisible.value = true;
  };
  const handleCreatePurchaseOrder = () => {
    calculateDialogVisible.value = false;
  };
  const { proxy } = getCurrentInstance();
  const handleExport = () => {
    ElMessageBox.confirm("内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/procurementPlan/export", {}, "采购计划.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  const handleSizeChange = size => {
    pagination.size = size;
    loadData();
  };
  const handleCurrentChange = current => {
    pagination.current = current;
    loadData();
  };
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    loadData();
  });
</script>
<style scoped>
  .app-container {
    padding: 20px;
  }
  .page-header {
    margin-bottom: 20px;
  }
  .page-header h2 {
    margin: 0 0 8px 0;
    color: #303133;
    font-size: 24px;
    font-weight: 600;
  }
  .page-header p {
    margin: 0;
    color: #909399;
    font-size: 14px;
  }
  .search-card {
    margin-bottom: 20px;
  }
  .search-form {
    margin-bottom: 0;
  }
  .table-card {
    margin-bottom: 20px;
  }
  .table-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
  .table-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .table-actions {
    display: flex;
    gap: 10px;
  }
  .pagination-container {
    margin-top: 20px;
    display: flex;
    justify-content: end;
  }
  .form-container {
    padding: 0 20px;
  }
  .formula-help {
    margin-top: 5px;
  }
  .calculate-result {
    padding: 20px 0;
  }
  .dialog-footer {
    text-align: right;
  }
  :deep(.el-card__body) {
    padding: 20px;
  }
  :deep(.el-table) {
    font-size: 14px;
  }
  :deep(.el-form-item__label) {
    font-weight: 500;
  }
  .form-container {
    padding: 0;
  }
  .form-section {
    margin-bottom: 24px;
    border: 1px solid #e4e7ed;
    border-radius: 6px;
    overflow: hidden;
  }
  .section-title {
    background-color: #f5f7fa;
    padding: 12px 16px;
    font-weight: 600;
    color: #303133;
    border-bottom: 1px solid #e4e7ed;
  }
  .form-section .el-form {
    padding: 20px;
  }
  .param-tabs {
    padding: 20px;
  }
  .param-tabs :deep(.el-tabs__header) {
    margin-bottom: 20px;
  }
  .param-tabs :deep(.el-tabs__item.is-active) {
    color: #f56c6c;
    border-bottom-color: #f56c6c;
  }
  .checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
  }
  .checkbox-group .el-checkbox {
    margin-right: 0;
    margin-bottom: 8px;
  }
  .formula-input-section {
    padding: 20px;
  }
  .formula-input-section .el-form-item {
    margin-bottom: 12px;
  }
  .formula-help {
    text-align: center;
    margin-top: 8px;
  }
</style>