已修改18个文件
已删除6个文件
9076 ■■■■ 文件已修改
src/api/basicData/parameterMaintenance.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/parameterMaintenance/index.vue 661 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 364 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 289 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Subtract.vue 310 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 1733 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 896 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 824 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/New.vue 279 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 1268 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/summaryByProduct/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 968 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/parameterMaintenance.js
@@ -23,7 +23,7 @@
export function updateParameter(data) {
    return request({
        url: '/basic/parameter/update',
        method: 'post',
        method: 'put',
        data: data
    })
}
@@ -44,3 +44,38 @@
        method: 'get'
    })
}
// æ–°å¢žåŸºç¡€å‚æ•°
export function addBaseParam(data) {
    return request({
        url: '/baseParam/add',
        method: 'post',
        data: data
    })
}
// ç¼–辑基础参数
export function editBaseParam(data) {
    return request({
        url: '/baseParam/edit',
        method: 'put',
        data: data
    })
}
// æŸ¥è¯¢åŸºç¡€å‚数列表
export function getBaseParamList(query) {
    return request({
        url: '/baseParam/list',
        method: 'get',
        params: query
    })
}
// åˆ é™¤åŸºç¡€å‚æ•°
export function removeBaseParam(ids) {
    return request({
        url: `/baseParam/remove/${Array.isArray(ids) ? ids.join(',') : ids}`,
        method: 'delete'
    })
}
src/api/productionManagement/productionProcess.js
@@ -66,4 +66,38 @@
    method: "post",
    responseType: "blob",
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨
export function getProcessParamList(processId) {
  return request({
    url: `/productProcessParam/list/${processId}`,
    method: "get",
  });
}
// æ·»åŠ å·¥åºå‚æ•°
export function addProcessParam(data) {
  return request({
    url: "/productProcessParam/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑工序参数
export function editProcessParam(data) {
  return request({
    url: "/productProcessParam/edit",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å·¥åºå‚æ•°
export function deleteProcessParam(id) {
  return request({
    url: `/productProcessParam/${id}`,
    method: "delete",
  });
}
src/components/PIMTable/PIMTable.vue
@@ -85,9 +85,9 @@
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'string'
            "
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :title="formatters(scope.row[item.prop], item.formatData, scope.row)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
            {{ formatters(scope.row[item.prop], item.formatData, scope.row) }}
          </el-tag>
          <el-tag v-for="(tag, index) in dataTypeFn(
              scope.row[item.prop],
@@ -98,14 +98,14 @@
              'object'
            "
                  :key="index"
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :title="formatters(scope.row[item.prop], item.formatData, scope.row)"
                  :type="formatType(tag, item.formatType)">
            {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
          </el-tag>
          <el-tag v-else
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :title="formatters(scope.row[item.prop], item.formatData, scope.row)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
            {{ formatters(scope.row[item.prop], item.formatData, scope.row) }}
          </el-tag>
        </div>
        <!-- æŒ‰é’® -->
@@ -181,7 +181,7 @@
             style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{
            formatters(scope.row[item.prop], item.formatData)
            formatters(scope.row[item.prop], item.formatData, scope.row)
          }}</span>
        </div>
      </template>
@@ -217,8 +217,8 @@
    return typeof val === "function" ? val(row) : val;
  };
  const formatters = (val, format) => {
    return typeof format === "function" ? format(val) : val;
  const formatters = (val, format, row) => {
    return typeof format === "function" ? format(val, row) : val;
  };
  // Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
src/views/basicData/customerFile/index.vue
@@ -717,11 +717,6 @@
  const tableColumn = ref([
    {
      label: "客户分类",
      prop: "customerType",
      width: 120,
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: 220,
@@ -744,6 +739,11 @@
      label: "联系电话",
      prop: "contactPhone",
      width: 150,
    },
    {
      label: "客户分类",
      prop: "customerType",
      width: 120,
    },
    {
      label: "跟进进度",
@@ -872,10 +872,10 @@
      maintenanceTime: [
        { required: false, message: "请选择", trigger: "change" },
      ],
      basicBankAccount: [{ required: true, message: "请输入", trigger: "blur" }],
      bankAccount: [{ required: true, message: "请输入", trigger: "blur" }],
      bankCode: [{ required: true, message: "请输入", trigger: "blur" }],
      customerType: [{ required: true, message: "请选择", trigger: "change" }],
      basicBankAccount: [{ required: false, message: "请输入", trigger: "blur" }],
      bankAccount: [{ required: false, message: "请输入", trigger: "blur" }],
      bankCode: [{ required: false, message: "请输入", trigger: "blur" }],
      customerType: [{ required: false, message: "请选择", trigger: "change" }],
    },
  });
  const upload = reactive({
src/views/basicData/parameterMaintenance/index.vue
@@ -3,15 +3,16 @@
    <div class="search_form">
      <div>
        <span class="search_title ml10">参数名称:</span>
        <el-input v-model="searchForm.materialCode"
        <el-input v-model="searchForm.paramName"
                  style="width: 200px"
                  placeholder="请输入参数名称"
                  clearable />
        <span class="search_title ml10">关联产品类型:</span>
        <!-- å…³è”产品类型搜索 -->
        <!-- <span class="search_title ml10">关联产品类型:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 200px"
                  style="width: 200px"d
                  placeholder="请输入关联产品类型"
                  clearable />
                  clearable /> -->
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
@@ -19,19 +20,21 @@
        <el-button type="primary"
                   @click="handleAdd"
                   style="margin-left: 10px">新增参数</el-button>
        <el-button type="primary"
        <!-- äº§å“ç±»åž‹ç»´æŠ¤æŒ‰é’® -->
        <!-- <el-button type="primary"
                   @click="handleProductTypeMaintenance"
                   style="margin-left: 10px">产品类型维护</el-button>
                   style="margin-left: 10px">产品类型维护</el-button> -->
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="materialCode"
      <PIMTable rowKey="paramName"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                height="calc(100vh - 320px)"
                height="calc(100vh - 280px)"
                :tableLoading="tableLoading"
                :isSelection="false"
                :isShowPagination="false"
                @pagination="pagination">
      </PIMTable>
    </div>
@@ -43,101 +46,75 @@
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="参数编号"
                      prop="parameterCode">
          <el-input v-model="formData.parameterCode"
                    placeholder="请输入参数编号" />
        </el-form-item>
        <el-form-item label="参数名称"
                      prop="parameterName">
          <el-input v-model="formData.parameterName"
                      prop="paramName">
          <el-input v-model="formData.paramName"
                    placeholder="请输入参数名称" />
        </el-form-item>
        <el-form-item label="参数模式"
                      prop="parameterFormat">
          <el-select v-model="formData.parameterType2"
        <el-form-item label="参数类型"
                      prop="paramType">
          <el-select v-model="formData.paramType"
                     @change="handleParamTypeChange"
                     placeholder="请选择参数类型">
            <el-option label="数值格式"
                       value="1" />
            <el-option label="文本格式"
                       value="2" />
            <el-option label="下拉选项"
                       value="3" />
            <el-option label="时间格式"
                       value="4" />
          </el-select>
        </el-form-item>
        <el-form-item label="取值模式"
                      prop="valueMode">
          <el-select v-model="formData.valueMode"
                     placeholder="请选择取值模式">
            <el-option label="单值"
                       value="1" />
            <el-option label="区间"
                       value="2" />
          </el-select>
        </el-form-item>
        <el-form-item label="参数类型"
                      prop="parameterType">
          <el-select v-model="formData.parameterType"
                     @change="handleParameterTypeChange"
                     placeholder="请选择参数类型">
            <el-option label="数值格式"
                       value="数值格式" />
            <el-option label="文本格式"
                       value="文本格式" />
            <el-option label="下拉选项"
                       value="下拉选项" />
            <el-option label="时间格式"
                       value="时间格式" />
          </el-select>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formData.unit"
                    placeholder="请输入单位" />
        </el-form-item>
        <el-form-item v-if="formData.parameterType === '下拉选项'"
                      label="数据字典"
                      prop="parameterFormat">
          <el-select v-model="formData.parameterFormat"
                     placeholder="请选择数据字典">
        <el-form-item label="取值格式"
                      v-if="formData.paramType == '1' || formData.paramType == '2'"
                      prop="paramFormat">
          <el-input v-model="formData.paramFormat"
                    placeholder="请输入取值格式" />
        </el-form-item>
        <el-form-item label="下拉字典"
                      v-else-if="formData.paramType == '3'"
                      prop="paramFormat">
          <el-select v-model="formData.paramFormat"
                     placeholder="请选择取值模式">
            <el-option v-for="item in dictTypes"
                       :key="item.dictType"
                       :label="item.dictName"
                       :value="item.dictType" />
          </el-select>
        </el-form-item>
        <el-form-item v-else-if="formData.parameterType === '时间格式'"
                      label="时间格式"
                      prop="parameterFormat">
          <el-select v-model="formData.parameterFormat"
                     placeholder="请选择时间格式">
            <el-option label="YYYY-MM-DD HH:mm:ss"
                       value="YYYY-MM-DD HH:mm:ss" />
        <el-form-item label="时间格式"
                      v-else-if="formData.paramType == '4'"
                      prop="paramFormat">
          <el-select v-model="formData.paramFormat"
                     placeholder="请选择取值模式">
            <el-option label="YYYY-MM-DD"
                       value="YYYY-MM-DD" />
            <el-option label="YYYY-MM-DD HH:mm:ss"
                       value="YYYY-MM-DD HH:mm:ss" />
          </el-select>
        </el-form-item>
        <el-form-item v-else
                      label="参数格式"
                      prop="parameterFormat">
          <el-input v-model="formData.parameterFormat"
                    placeholder="请输入参数格式" />
        </el-form-item>
        <el-form-item label="关联产品类型"
                      prop="parameterValue">
          <el-select v-model="formData.parameterValue"
                     placeholder="请选择关联产品类型">
            <el-option v-for="item in productTypes"
                       :key="item.value"
                       :label="item.label"
                       :value="item.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="标准值"
                      v-if="formData.parameterType2 === '1'"
                      prop="standardValue">
          <el-input v-model="formData.standardValue"
                    placeholder="请输入标准值" />
        </el-form-item>
        <el-form-item label="最大值"
                      v-if="formData.parameterType2 === '2'"
                      prop="standardValue">
          <el-input v-model="formData.standardValue"
                    placeholder="请输入标准值" />
        </el-form-item>
        <el-form-item label="最小值"
                      v-if="formData.parameterType2 === '2'"
                      prop="standardValue">
          <el-input v-model="formData.standardValue"
                    placeholder="请输入标准值" />
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formData.unit"
                    placeholder="请输入单位" />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formData.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
@@ -149,7 +126,7 @@
      </template>
    </el-dialog>
    <!-- äº§å“ç±»åž‹ç»´æŠ¤å¯¹è¯æ¡† -->
    <el-dialog v-model="productTypeDialogVisible"
    <!-- <el-dialog v-model="productTypeDialogVisible"
               title="产品类型维护"
               width="600px">
      <div class="product-type-header">
@@ -159,9 +136,9 @@
      <el-table :data="productTypeList"
                border
                style="width: 100%; margin-top: 10px; margin-bottom: 20px">
        <!-- <el-table-column prop="typeCode"
        <el-table-column prop="typeCode"
                         label="类型编码"
                         width="150" /> -->
                         width="150" />
        <el-table-column prop="typeName"
                         label="类型名称" />
        <el-table-column label="操作"
@@ -176,20 +153,20 @@
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    </el-dialog> -->
    <!-- æ–°å¢ž/编辑产品类型对话框 -->
    <el-dialog v-model="productTypeFormVisible"
    <!-- <el-dialog v-model="productTypeFormVisible"
               :title="productTypeDialogTitle"
               width="400px">
      <el-form :model="productTypeForm"
               :rules="productTypeRules"
               ref="productTypeFormRef"
               label-width="100px">
        <!-- <el-form-item label="类型编码"
        <el-form-item label="类型编码"
                      prop="typeCode">
          <el-input v-model="productTypeForm.typeCode"
                    placeholder="请输入类型编码" />
        </el-form-item> -->
        </el-form-item>
        <el-form-item label="类型名称"
                      prop="typeName">
          <el-input v-model="productTypeForm.typeName"
@@ -203,7 +180,7 @@
                     @click="handleProductTypeSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    </el-dialog> -->
  </div>
</template>
@@ -214,7 +191,11 @@
    addParameter,
    updateParameter,
    delParameter,
    getProductTypes as getProductTypesApi,
    addBaseParam,
    editBaseParam,
    getBaseParamList,
    removeBaseParam,
    // getProductTypes as getProductTypesApi,
  } from "@/api/basicData/parameterMaintenance.js";
  import { listType } from "@/api/system/dict/type";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
@@ -222,64 +203,65 @@
  const tableColumn = ref([
    {
      label: "参数编号",
      prop: "parameterCode",
      className: "code-cell",
    },
    {
      label: "参数名称",
      prop: "parameterName",
    },
    {
      label: "参数模式",
      prop: "parameterType2",
      prop: "paramName",
    },
    {
      label: "参数类型",
      prop: "parameterType",
      prop: "paramType",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          æ•°å€¼æ ¼å¼: "primary",
          æ–‡æœ¬æ ¼å¼: "info",
          ä¸‹æ‹‰é€‰é¡¹: "warning",
          æ—¶é—´æ ¼å¼: "success",
          1: "primary",
          2: "info",
          3: "warning",
          4: "success",
        };
        return typeMap[params] || "default";
      },
    },
    {
      label: "参数格式",
      prop: "parameterFormat",
    },
    {
      label: "关联产品类型",
      prop: "parameterValue",
    },
    {
      label: "标准值",
      prop: "standardValue",
      className: row => {
        return row.parameterType == "数值格式" ? "quantity-cell" : "";
      formatData: val => {
        const labelMap = {
          1: "数值格式",
          2: "文本格式",
          3: "下拉选项",
          4: "时间格式",
        };
        return labelMap[val] || val;
      },
    },
    {
      label: "最大值",
      prop: "standardValue",
      className: row => {
        return row.parameterType == "数值格式" ? "quantity-cell" : "";
      label: "取值模式",
      prop: "valueMode",
      dataType: "tag",
      formatType: params => {
        return params === 2 ? "warning" : "success";
      },
    },
    {
      label: "最小值",
      prop: "standardValue",
      className: row => {
        return row.parameterType == "数值格式" ? "quantity-cell" : "";
      formatData: val => {
        return val === 2 ? "区间" : "单值";
      },
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "取值格式",
      prop: "paramFormat",
      formatData: (val, row) => {
        if (row.paramType == "3") {
          const dict = dictTypes.value.find(item => item.dictType === val);
          return dict ? "字典:" + dict.dictName : val;
        }
        return val;
      },
    },
    {
      label: "备注",
      prop: "remark",
    },
    {
      label: "创建时间",
      prop: "createTime",
    },
    {
      label: "操作",
@@ -308,10 +290,9 @@
    size: 10,
    total: 0,
  });
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    materialCode: "",
    paramName: "",
    productName: "",
  });
@@ -320,121 +301,123 @@
  const dialogTitle = ref("");
  const formRef = ref(null);
  const formData = reactive({
    parameterCode: "",
    parameterName: "",
    parameterType2: "1",
    parameterType: "",
    parameterFormat: "",
    parameterValue: "",
    standardValue: "",
    id: null,
    paramName: "",
    paramType: "",
    valueMode: "1",
    unit: "",
    remark: "",
  });
  const rules = reactive({
    parameterCode: [
      { required: true, message: "请输入参数编号", trigger: "blur" },
    paramName: [{ required: true, message: "请输入参数名称", trigger: "blur" }],
    paramType: [{ required: true, message: "请选择参数类型", trigger: "change" }],
    valueMode: [{ required: true, message: "请选择取值模式", trigger: "change" }],
    unit: [
      {
        required: false,
        message: "请输入单位",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (formData.paramType === "1" && !value) {
            callback(new Error("数值类型必须填写单位"));
          } else {
            callback();
          }
        },
      },
    ],
    parameterName: [
      { required: true, message: "请输入参数名称", trigger: "blur" },
    ],
    parameterType: [
      { required: true, message: "请选择参数类型", trigger: "change" },
    ],
    parameterFormat: [
      { required: true, message: "请选择参数格式", trigger: "change" },
    ],
    parameterValue: [
      { required: true, message: "请选择关联产品类型", trigger: "change" },
    ],
    standardValue: [{ required: true, message: "请输入标准值", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
  });
  const productTypes = ref([]);
  // const productTypes = ref([]);
  const isEdit = ref(false);
  // äº§å“ç±»åž‹ç»´æŠ¤ç›¸å…³
  const productTypeDialogVisible = ref(false);
  const productTypeFormVisible = ref(false);
  const productTypeDialogTitle = ref("");
  const productTypeFormRef = ref(null);
  const productTypeList = ref([]);
  const productTypeForm = reactive({
    id: null,
    // typeCode: "",
    typeName: "",
  });
  const productTypeRules = reactive({
    // typeCode: [{ required: true, message: "请输入类型编码", trigger: "blur" }],
    typeName: [{ required: true, message: "请输入类型名称", trigger: "blur" }],
  });
  const isProductTypeEdit = ref(false);
  const handleParameterTypeChange = () => {
    if (formData.parameterType === "数值格式") {
      formData.parameterFormat = "#.0000";
    } else if (formData.parameterType === "时间格式") {
      formData.parameterFormat = "YYYY-MM-DD HH:mm:ss";
  // äº§å“ç±»åž‹ç»´æŠ¤ç›¸å…³ - å·²æ³¨é‡Š
  // const productTypeDialogVisible = ref(false);
  // const productTypeFormVisible = ref(false);
  // const productTypeDialogTitle = ref("");
  // const productTypeFormRef = ref(null);
  // const productTypeList = ref([]);
  // const productTypeForm = reactive({
  //   id: null,
  //   typeCode: "",
  //   typeName: "",
  // });
  // const productTypeRules = reactive({
  //   typeCode: [{ required: true, message: "请输入类型编码", trigger: "blur" }],
  //   typeName: [{ required: true, message: "请输入类型名称", trigger: "blur" }],
  // });
  // const isProductTypeEdit = ref(false);
  const handleParamTypeChange = () => {
    if (formData.paramType === "1") {
      formData.paramFormat = "#.0000";
    } else if (formData.paramType === "4") {
      formData.paramFormat = "YYYY-MM-DD HH:mm:ss";
    } else {
      formData.parameterFormat = "";
      formData.paramFormat = "";
    }
    // è§¦å‘单位字段验证
    if (formRef.value) {
      formRef.value.validateField("unit");
    }
  };
  // äº§å“ç±»åž‹ç»´æŠ¤æŒ‰é’®ç‚¹å‡»äº‹ä»¶
  const handleProductTypeMaintenance = () => {
    productTypeDialogVisible.value = true;
    getProductTypeList();
  };
  // äº§å“ç±»åž‹ç»´æŠ¤æŒ‰é’®ç‚¹å‡»äº‹ä»¶ - å·²æ³¨é‡Š
  // const handleProductTypeMaintenance = () => {
  //   productTypeDialogVisible.value = true;
  //   getProductTypeList();
  // };
  // èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨
  const getProductTypeList = () => {
    productTypeList.value = [
      { id: 1, typeCode: "TYPE001", typeName: "3.5砌块" },
      { id: 2, typeCode: "TYPE002", typeName: "5.0砌块" },
      { id: 3, typeCode: "TYPE003", typeName: "板材" },
    ];
  };
  // èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨ - å·²æ³¨é‡Š
  // const getProductTypeList = () => {
  //   productTypeList.value = [
  //     { id: 1, typeCode: "TYPE001", typeName: "3.5砌块" },
  //     { id: 2, typeCode: "TYPE002", typeName: "5.0砌块" },
  //     { id: 3, typeCode: "TYPE003", typeName: "板材" },
  //   ];
  // };
  // æ–°å¢žäº§å“ç±»åž‹
  const handleAddProductType = () => {
    isProductTypeEdit.value = false;
    productTypeDialogTitle.value = "新增产品类型";
    productTypeForm.id = null;
    productTypeForm.typeCode = "";
    productTypeForm.typeName = "";
    productTypeFormVisible.value = true;
  };
  // æ–°å¢žäº§å“ç±»åž‹ - å·²æ³¨é‡Š
  // const handleAddProductType = () => {
  //   isProductTypeEdit.value = false;
  //   productTypeDialogTitle.value = "新增产品类型";
  //   productTypeForm.id = null;
  //   productTypeForm.typeCode = "";
  //   productTypeForm.typeName = "";
  //   productTypeFormVisible.value = true;
  // };
  // ç¼–辑产品类型
  const handleEditProductType = row => {
    isProductTypeEdit.value = true;
    productTypeDialogTitle.value = "编辑产品类型";
    productTypeForm.id = row.id;
    productTypeForm.typeCode = row.typeCode;
    productTypeForm.typeName = row.typeName;
    productTypeFormVisible.value = true;
  };
  // ç¼–辑产品类型 - å·²æ³¨é‡Š
  // const handleEditProductType = row => {
  //   isProductTypeEdit.value = true;
  //   productTypeDialogTitle.value = "编辑产品类型";
  //   productTypeForm.id = row.id;
  //   productTypeForm.typeCode = row.typeCode;
  //   productTypeForm.typeName = row.typeName;
  //   productTypeFormVisible.value = true;
  // };
  // åˆ é™¤äº§å“ç±»åž‹
  const handleDeleteProductType = row => {
    ElMessageBox.confirm("确定要删除该产品类型吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        ElMessage.success("删除成功");
        getProductTypeList();
      })
      .catch(() => {});
  };
  // åˆ é™¤äº§å“ç±»åž‹ - å·²æ³¨é‡Š
  // const handleDeleteProductType = row => {
  //   ElMessageBox.confirm("确定要删除该产品类型吗?", "提示", {
  //     confirmButtonText: "确定",
  //     cancelButtonText: "取消",
  //     type: "warning",
  //   })
  //     .then(() => {
  //       ElMessage.success("删除成功");
  //       getProductTypeList();
  //     })
  //     .catch(() => {});
  // };
  // æäº¤äº§å“ç±»åž‹è¡¨å•
  const handleProductTypeSubmit = () => {
    productTypeFormRef.value.validate(valid => {
      if (valid) {
        ElMessage.success(isProductTypeEdit.value ? "编辑成功" : "新增成功");
        productTypeFormVisible.value = false;
        getProductTypeList();
      }
    });
  };
  // æäº¤äº§å“ç±»åž‹è¡¨å• - å·²æ³¨é‡Š
  // const handleProductTypeSubmit = () => {
  //   productTypeFormRef.value.validate(valid => {
  //     if (valid) {
  //       ElMessage.success(isProductTypeEdit.value ? "编辑成功" : "新增成功");
  //       productTypeFormVisible.value = false;
  //       getProductTypeList();
  //     }
  //   });
  // };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -445,7 +428,7 @@
  /** é‡ç½®æŒ‰é’®æ“ä½œ */
  const handleReset = () => {
    searchForm.materialCode = "";
    searchForm.paramName = "";
    searchForm.productName = "";
    page.current = 1;
    getList();
@@ -458,119 +441,43 @@
  const getList = () => {
    tableLoading.value = true;
    // æ³¨é‡ŠæŽ‰API调用,使用假数据
    /*const params = { ...searchForm, ...page };
                                                                    parameterListPage(params)
                                                                      .then(res => {
                                                                        tableLoading.value = false;
                                                                        tableData.value = res.data;
                                                                        page.total = res.total || 0;
                                                                      })
                                                                      .catch(() => {
                                                                        tableLoading.value = false;
                                                                      });*/
    // å‡æ•°æ®
    setTimeout(() => {
      tableLoading.value = false;
      tableData.value = [
        {
          id: 1,
          parameterCode: "PARAM001",
          parameterName: "长度",
          parameterType2: "1",
          parameterType: "数值格式",
          parameterFormat: "",
          parameterValue: "type1",
          standardValue: "100",
          unit: "mm",
        },
        {
          id: 2,
          parameterCode: "PARAM002",
          parameterName: "温度",
          parameterType2: "2",
          parameterType: "数值格式",
          parameterFormat: "",
          parameterValue: "type1",
          standardValue: "25",
          unit: "℃",
        },
        {
          id: 3,
          parameterCode: "PARAM003",
          parameterName: "颜色",
          parameterType2: "1",
          parameterType: "文本格式",
          parameterFormat: "",
          parameterValue: "type2",
          standardValue: "红色",
          unit: "",
        },
        {
          id: 4,
          parameterCode: "PARAM004",
          parameterName: "状态",
          parameterType2: "1",
          parameterType: "下拉选项",
          parameterFormat: "status",
          parameterValue: "type3",
          standardValue: "正常",
          unit: "",
        },
        {
          id: 5,
          parameterCode: "PARAM005",
          parameterName: "创建时间",
          parameterType2: "1",
          parameterType: "时间格式",
          parameterFormat: "YYYY-MM-DD HH:mm:ss",
          parameterValue: "type2",
          standardValue: "2024-01-01 00:00:00",
          unit: "",
        },
      ];
      page.total = 5;
    }, 500);
    // è°ƒç”¨æ–°æŽ¥å£ /baseParam/list
    getBaseParamList({ paramName: searchForm.paramName })
      .then(res => {
        tableLoading.value = false;
        if (res.code === 200) {
          tableData.value = res.data || [];
          page.total = res.data?.length || 0;
        } else {
          ElMessage.error(res.msg || "查询失败");
        }
      })
      .catch(() => {
        tableLoading.value = false;
        ElMessage.error("查询失败");
      });
  };
  // èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨
  const getProductTypes = () => {
    // æ³¨é‡ŠæŽ‰API调用,使用假数据
    /*getProductTypesApi()
                                                                      .then(res => {
                                                                        productTypes.value = res.data || [];
                                                                      })
                                                                      .catch(() => {
                                                                        // å¤±è´¥æ—¶ä½¿ç”¨æ¨¡æ‹Ÿæ•°æ®
                                                                        productTypes.value = [
                                                                          { label: "3.5砌块", value: "type1" },
                                                                          { label: "5.0砌块", value: "type2" },
                                                                          { label: "板材", value: "type3" },
                                                                        ];
                                                                      });*/
    // å‡æ•°æ®
    productTypes.value = [
      { label: "3.5砌块", value: "type1" },
      { label: "5.0砌块", value: "type2" },
      { label: "板材", value: "type3" },
    ];
  };
  // èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨ - å·²æ³¨é‡Š
  // const getProductTypes = () => {
  //   productTypes.value = [
  //     { label: "3.5砌块", value: "type1" },
  //     { label: "5.0砌块", value: "type2" },
  //     { label: "板材", value: "type3" },
  //   ];
  // };
  // æ–°å¢žæŒ‰é’®ç‚¹å‡»äº‹ä»¶
  const handleAdd = () => {
    isEdit.value = false;
    dialogTitle.value = "新增参数";
    // é‡ç½®è¡¨å•
    formData.parameterCode = "";
    formData.parameterName = "";
    formData.parameterType2 = "1";
    formData.parameterType = "";
    formData.parameterFormat = "";
    formData.parameterValue = "";
    formData.standardValue = "";
    formData.id = null;
    formData.paramName = "";
    formData.paramType = "";
    formData.valueMode = "1";
    formData.unit = "";
    formData.remark = "";
    dialogVisible.value = true;
  };
@@ -578,15 +485,14 @@
  const handleEdit = row => {
    isEdit.value = true;
    dialogTitle.value = "编辑参数";
    // å¡«å……表单数据
    formData.parameterCode = row.parameterCode;
    formData.parameterName = row.parameterName;
    formData.parameterType2 = row.parameterType2 || "1";
    formData.parameterType = row.parameterType;
    formData.parameterFormat = row.parameterFormat;
    formData.parameterValue = row.parameterValue;
    formData.standardValue = row.standardValue;
    formData.unit = row.unit;
    // å¡«å……表单数据 - å°†æ•°å­—类型转换为字符串类型以匹配表单绑定
    formData.id = row.id;
    formData.paramName = row.paramName || "";
    formData.paramType = row.paramType !== undefined ? String(row.paramType) : "";
    formData.valueMode =
      row.valueMode !== undefined ? String(row.valueMode) : "1";
    formData.unit = row.unit || "";
    formData.remark = row.remark || "";
    dialogVisible.value = true;
  };
@@ -598,19 +504,15 @@
      type: "warning",
    })
      .then(() => {
        // æ³¨é‡ŠæŽ‰API调用,使用假数据
        /*delParameter(row.id)
                                                                      .then(res => {
                                                                        ElMessage.success("删除成功");
                                                                        getList();
                                                                      })
                                                                      .catch(() => {
                                                                        ElMessage.error("删除失败");
                                                                      });*/
        // å‡æ•°æ®æ¨¡æ‹Ÿ
        ElMessage.success("删除成功");
        getList();
        // è°ƒç”¨æ–°æŽ¥å£ /baseParam/remove/{id}
        removeBaseParam(row.id)
          .then(res => {
            ElMessage.success("删除成功");
            getList();
          })
          .catch(() => {
            ElMessage.error("删除失败");
          });
      })
      .catch(() => {
        // å–消删除
@@ -621,22 +523,29 @@
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        // æ³¨é‡ŠæŽ‰API调用,使用假数据
        /*const api = isEdit.value ? updateParameter : addParameter;
                                                                      api(formData)
                                                                        .then(res => {
                                                                          ElMessage.success(isEdit.value ? "编辑成功" : "新增成功");
                                                                          dialogVisible.value = false;
                                                                          getList();
                                                                        })
                                                                        .catch(() => {
                                                                          ElMessage.error(isEdit.value ? "编辑失败" : "新增失败");
                                                                        });*/
        // å‡æ•°æ®æ¨¡æ‹Ÿ
        ElMessage.success(isEdit.value ? "编辑成功" : "新增成功");
        dialogVisible.value = false;
        getList();
        if (isEdit.value) {
          // ç¼–辑使用新接口 /baseParam/edit
          editBaseParam(formData)
            .then(res => {
              ElMessage.success("编辑成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              // ElMessage.error("编辑失败");
            });
        } else {
          // æ–°å¢žä½¿ç”¨æ–°æŽ¥å£ /baseParam/add
          addBaseParam(formData)
            .then(res => {
              ElMessage.success("新增成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              ElMessage.error("新增失败");
            });
        }
      } else {
        return false;
      }
@@ -652,7 +561,7 @@
  onMounted(() => {
    getDictTypes();
    getList();
    getProductTypes();
    // getProductTypes();
  });
</script>
src/views/basicData/product/ProductSelectDialog.vue
@@ -1,39 +1,83 @@
<template>
  <el-dialog v-model="visible" title="选择产品" width="900px" destroy-on-close :close-on-click-modal="false">
    <el-form :inline="true" :model="query" class="mb-2">
      <el-form-item label="产品大类">
        <el-input v-model="query.productName" placeholder="输入产品大类" clearable @keyup.enter="onSearch" />
  <el-dialog v-model="visible"
             title="选择产品"
             width="1000px"
             destroy-on-close
             :close-on-click-modal="false">
    <el-form :inline="true"
             :model="query"
             class="mb-2 search-form">
      <el-form-item label="产品名称">
        <el-input v-model="query.productName"
                  size="small"
                  placeholder="输入产品名称"
                  clearable
                  @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item label="型号名称">
        <el-input v-model="query.model" placeholder="输入型号名称" clearable @keyup.enter="onSearch" />
      <el-form-item label="规格">
        <el-input v-model="query.model"
                  size="small"
                  placeholder="输入规格"
                  clearable
                  @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button @click="onReset">重置</el-button>
      <el-form-item label="物料编码">
        <el-input v-model="query.materialCode"
                  size="small"
                  placeholder="输入物料编码"
                  clearable
                  @keyup.enter="onSearch" />
      </el-form-item>
      <el-form-item style="200px">
        <el-button type="primary"
                   size="small"
                   @click="onSearch">搜索</el-button>
        <el-button size="small"
                   @click="onReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- åˆ—表 -->
    <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id"
      @selection-change="handleSelectionChange" @select="handleSelect">
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="序号" width="60" />
      <el-table-column prop="productName" label="产品大类" min-width="160" />
      <el-table-column prop="model" label="型号名称" min-width="200" />
      <el-table-column prop="unit" label="单位" min-width="160" />
    <el-table ref="tableRef"
              v-loading="loading"
              :data="tableData"
              height="420"
              highlight-current-row
              row-key="id"
              @selection-change="handleSelectionChange"
              @select="handleSelect">
      <el-table-column type="selection"
                       width="55" />
      <el-table-column type="index"
                       label="序号"
                       width="60" />
      <el-table-column prop="productName"
                       label="产品名称"
                       min-width="160" />
      <el-table-column prop="materialCode"
                       label="物料编码"
                       min-width="200" />
      <el-table-column prop="model"
                       label="规格"
                       min-width="200" />
      <el-table-column prop="unit"
                       label="单位"
                       min-width="160" />
    </el-table>
    <div class="mt-3 flex justify-end">
      <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
        v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]"
        @size-change="onPageChange" @current-change="onPageChange" />
      <el-pagination background
                     layout="total, sizes, prev, pager, next, jumper"
                     :total="total"
                     v-model:page-size="page.pageSize"
                     v-model:current-page="page.pageNum"
                     :page-sizes="[10, 20, 50, 100]"
                     @size-change="onPageChange"
                     @current-change="onPageChange" />
    </div>
    <template #footer>
      <el-button @click="close()">取消</el-button>
      <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
      <el-button type="primary"
                 :disabled="multipleSelection.length === 0"
                 @click="onConfirm">
        ç¡®å®š
      </el-button>
    </template>
@@ -41,140 +85,174 @@
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { productModelList } from '@/api/basicData/productModel'
  import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
  import { ElMessage } from "element-plus";
  import { modelListPage } from "@/api/basicData/newProduct";
export type ProductRow = {
  id: number;
  productName: string;
  model: string;
  unit?: string;
};
  export type ProductRow = {
    id: number;
    model: string;
    materialCode: string;
    unit?: string;
    productName?: string;
  };
const props = defineProps<{
  modelValue: boolean;
  single?: boolean; // æ˜¯å¦åªèƒ½é€‰æ‹©ä¸€ä¸ªï¼Œé»˜è®¤false(可选择多个)
}>();
  const props = defineProps<{
    modelValue: boolean;
    single?: boolean; // æ˜¯å¦åªèƒ½é€‰æ‹©ä¸€ä¸ªï¼Œé»˜è®¤false(可选择多个)
  }>();
const emit = defineEmits(['update:modelValue', 'confirm']);
  const emit = defineEmits(["update:modelValue", "confirm"]);
const visible = computed({
  get: () => props.modelValue,
  set: (v) => emit("update:modelValue", v),
});
  const visible = computed({
    get: () => props.modelValue,
    set: v => emit("update:modelValue", v),
  });
const query = reactive({
  productName: "",
  model: "",
});
  const query = reactive({
    model: "",
    materialCode: "",
    productName: "",
  });
const page = reactive({
  pageNum: 1,
  pageSize: 10,
});
  const page = reactive({
    pageNum: 1,
    pageSize: 10,
  });
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const multipleSelection = ref<ProductRow[]>([]);
const tableRef = ref();
  const loading = ref(false);
  const tableData = ref<ProductRow[]>([]);
  const total = ref(0);
  const multipleSelection = ref<ProductRow[]>([]);
  const tableRef = ref();
function close() {
  visible.value = false;
}
const handleSelectionChange = (val: ProductRow[]) => {
  if (props.single && val.length > 1) {
    // å¦‚果限制为单个选择,只保留最后一个选中的
    const lastSelected = val[val.length - 1];
    multipleSelection.value = [lastSelected];
    // æ¸…空表格选中状态,然后重新选中最后一个
    nextTick(() => {
      if (tableRef.value) {
        tableRef.value.clearSelection();
        tableRef.value.toggleRowSelection(lastSelected, true);
      }
    });
  } else {
    multipleSelection.value = val;
  function close() {
    visible.value = false;
  }
}
// å¤„理单个选择
const handleSelect = (selection: ProductRow[], row: ProductRow) => {
  if (props.single) {
    // å¦‚果限制为单个,清空其他选择,只保留当前行
    if (selection.includes(row)) {
      // é€‰ä¸­å½“前行时,清空其他选中
      multipleSelection.value = [row];
  const handleSelectionChange = (val: ProductRow[]) => {
    if (props.single && val.length > 1) {
      // å¦‚果限制为单个选择,只保留最后一个选中的
      const lastSelected = val[val.length - 1];
      multipleSelection.value = [lastSelected];
      // æ¸…空表格选中状态,然后重新选中最后一个
      nextTick(() => {
        if (tableRef.value) {
          tableData.value.forEach((item) => {
            if (item.id !== row.id) {
              tableRef.value.toggleRowSelection(item, false);
            }
          });
          tableRef.value.clearSelection();
          tableRef.value.toggleRowSelection(lastSelected, true);
        }
      });
    } else {
      multipleSelection.value = val;
    }
  };
  // å¤„理单个选择
  const handleSelect = (selection: ProductRow[], row: ProductRow) => {
    if (props.single) {
      // å¦‚果限制为单个,清空其他选择,只保留当前行
      if (selection.includes(row)) {
        // é€‰ä¸­å½“前行时,清空其他选中
        multipleSelection.value = [row];
        nextTick(() => {
          if (tableRef.value) {
            tableData.value.forEach(item => {
              if (item.id !== row.id) {
                tableRef.value.toggleRowSelection(item, false);
              }
            });
          }
        });
      }
    }
  };
  function onSearch() {
    page.pageNum = 1;
    loadData();
  }
  function onReset() {
    query.model = "";
    query.materialCode = "";
    query.productName = "";
    page.pageNum = 1;
    loadData();
  }
  function onPageChange() {
    loadData();
  }
  function onConfirm() {
    if (multipleSelection.value.length === 0) {
      ElMessage.warning("请选择一条产品");
      return;
    }
    if (props.single && multipleSelection.value.length > 1) {
      ElMessage.warning("只能选择一个产品");
      return;
    }
    emit(
      "confirm",
      props.single ? [multipleSelection.value[0]] : multipleSelection.value
    );
    close();
  }
  async function loadData() {
    loading.value = true;
    try {
      multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
      const res: any = await modelListPage({
        model: query.model.trim(),
        materialCode: query.materialCode.trim(),
        productName: query.productName.trim(),
        type: 1,
        current: page.pageNum,
        size: page.pageSize,
      });
      tableData.value = res.data.records;
      total.value = res.data.total;
    } finally {
      loading.value = false;
    }
  }
}
function onSearch() {
  page.pageNum = 1;
  loadData();
}
  // ç›‘听弹窗打开,重置选择
  watch(
    () => props.modelValue,
    visible => {
      if (visible) {
        multipleSelection.value = [];
      }
    }
  );
function onReset() {
  query.productName = "";
  query.model = "";
  page.pageNum = 1;
  loadData();
}
function onPageChange() {
  loadData();
}
function onConfirm() {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning("请选择一条产品");
    return;
  }
  if (props.single && multipleSelection.value.length > 1) {
    ElMessage.warning("只能选择一个产品");
    return;
  }
  emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value);
  close();
}
async function loadData() {
  loading.value = true;
  try {
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res: any = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      current: page.pageNum,
      size: page.pageSize,
    });
    tableData.value = res.records;
    total.value = res.total;
  } finally {
    loading.value = false;
  }
}
// ç›‘听弹窗打开,重置选择
watch(() => props.modelValue, (visible) => {
  if (visible) {
    multipleSelection.value = [];
  }
});
onMounted(() => {
  loadData()
})
  onMounted(() => {
    loadData();
  });
</script>
<style scoped>
  .search-form {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
  }
  .search-form .el-form-item {
    margin-bottom: 0;
  }
  .search-form .el-input {
    width: 180px;
  }
  .el-form--inline .el-form-item {
    margin-right: 5px;
  }
  .justify-end {
    margin-top: 10px;
  }
</style>
src/views/basicData/product/index.vue
@@ -84,7 +84,7 @@
        </el-button>
        <ImportExcel :product-id="currentId"
                     @uploadSuccess="getModelList" />
        <el-input v-model="specification"
        <el-input v-model="model"
                  placeholder="规格型号"
                  style="width: 150px"
                  clearable
@@ -121,8 +121,8 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:"
                          prop="materialName">
              <el-input v-model="form.materialName"
                          prop="productName">
              <el-input v-model="form.productName"
                        placeholder="请输入产品名称"
                        maxlength="20"
                        show-word-limit
@@ -168,8 +168,8 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="基本单位:"
                          prop="baseUnit">
              <el-input v-model="form.baseUnit"
                          prop="unit">
              <el-input v-model="form.unit"
                        placeholder="请输入基本单位"
                        clearable
                        @keydown.enter.prevent />
@@ -210,8 +210,8 @@
        <el-row>
          <el-col :span="24">
            <el-form-item label="规格型号:"
                          prop="specification">
              <el-input v-model="modelForm.specification"
                          prop="model">
              <el-input v-model="modelForm.model"
                        placeholder="请输入规格型号"
                        clearable
                        @keydown.enter.prevent />
@@ -327,7 +327,7 @@
  const expandedKeys = ref([]);
  const inventoryCategoryList = ref([]);
  const materialTypeList = ref([]);
  const specification = ref("");
  const model = ref("");
  const materialCode = ref("");
  const getloadData = () => {
@@ -374,11 +374,11 @@
  const tableColumn = ref([
    {
      label: "规格型号",
      prop: "materialName",
      prop: "productName",
    },
    {
      label: "规格",
      prop: "specification",
      prop: "model",
    },
    {
      label: "物料编码",
@@ -386,7 +386,7 @@
    },
    {
      label: "单位",
      prop: "baseUnit",
      prop: "unit",
    },
    {
      dataType: "action",
@@ -417,28 +417,28 @@
    form: {
      materialTypeId: null,
      inventoryCategoryId: null,
      materialName: "",
      baseUnit: "",
      productName: "",
      unit: "",
      remark: "",
    },
    rules: {
      materialName: [
      productName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
      ],
      inventoryCategoryId: [
        { required: true, message: "请选择", trigger: "change" },
      ],
      baseUnit: [{ required: true, message: "请输入", trigger: "blur" }],
      unit: [{ required: true, message: "请输入", trigger: "blur" }],
    },
    modelForm: {
      specification: "",
      model: "",
      supplyType: "",
      materialCode: "",
      id: null,
    },
    modelRules: {
      specification: [{ required: true, message: "请输入", trigger: "blur" }],
      model: [{ required: true, message: "请输入", trigger: "blur" }],
      supplyType: [{ required: true, message: "请选择", trigger: "change" }],
      materialCode: [{ required: true, message: "请输入", trigger: "blur" }],
    },
@@ -494,7 +494,7 @@
    treeLoad.value = true;
    // è°ƒç”¨ productTreeListQuery æŽ¥å£è¿›è¡Œæœç´¢
    productTreeListQuery({ materialName: search.value })
    productTreeListQuery({ productName: search.value })
      .then(res => {
        // å¤„理返回的数据
        const newList = [];
@@ -505,11 +505,11 @@
                item.children = (category.materialList || []).map(item => ({
                  id: item.id,
                  isLeaf: true,
                  label: item.materialName,
                  label: item.productName,
                  inventoryCategoryId: item.inventoryCategoryId,
                  materialTypeId: item.materialTypeId,
                  remark: item.remark,
                  baseUnit: item.baseUnit,
                  unit: item.unit,
                }));
                break;
              }
@@ -522,11 +522,11 @@
            //   children: (category.materialList || []).map(item => ({
            //     id: item.id,
            //     isLeaf: true,
            //     label: item.materialName,
            //     label: item.productName,
            //     inventoryCategoryId: item.inventoryCategoryId,
            //     materialTypeId: item.materialTypeId,
            //     remark: item.remark,
            //     baseUnit: item.baseUnit,
            //     unit: item.unit,
            //   })),
            // };
            // newList.push(categoryNode);
@@ -546,18 +546,18 @@
    productDia.value = true;
    // é‡ç½®è¡¨å•
    form.value = {
      materialName: "",
      productName: "",
      inventoryCategoryId: null,
      baseUnit: "",
      unit: "",
      remark: "",
      materialTypeId: null,
    };
    console.log(data);
    if (type === "edit" && data) {
      // ç¼–辑模式,回填数据
      form.value.materialName = data.label || "";
      form.value.productName = data.label || "";
      form.value.inventoryCategoryId = data.inventoryCategoryId || null;
      form.value.baseUnit = data.baseUnit || "";
      form.value.unit = data.unit || "";
      form.value.remark = data.remark || "";
      form.value.materialTypeId = data.materialTypeId || null;
      form.value.id = data.id || null;
@@ -586,16 +586,16 @@
    modelOperationType.value = type;
    modelDia.value = true;
    // é‡ç½®æ‰€æœ‰å­—段
    modelForm.value.specification = "";
    modelForm.value.model = "";
    modelForm.value.supplyType = "";
    modelForm.value.id = null;
    modelForm.value.materialCode = null;
    if (type === "edit" && data) {
      // ç¼–辑模式,回填数据
      modelForm.value.specification = data.specification || "";
      modelForm.value.model = data.model || "";
      modelForm.value.supplyType = data.supplyType || "";
      modelForm.value.id = data.skuId || null;
      modelForm.value.id = data.id || null;
      modelForm.value.materialCode = data.materialCode || null;
    }
  };
@@ -607,8 +607,8 @@
        // const params = {
        //   materialTypeId: null,
        //   inventoryCategoryId: form.value.inventoryCategoryId,
        //   materialName: form.value.materialName,
        //   baseUnit: form.value.baseUnit,
        //   productName: form.value.productName,
        //   unit: form.value.unit,
        //   remark: form.value.remark,
        // };
@@ -743,11 +743,11 @@
            const children = materialList.map(item => ({
              id: item.id,
              isLeaf: true,
              label: item.materialName,
              label: item.productName,
              inventoryCategoryId: item.inventoryCategoryId,
              materialTypeId: item.materialTypeId,
              remark: item.remark,
              baseUnit: item.baseUnit,
              unit: item.unit,
            }));
            // æ›´æ–°èŠ‚ç‚¹çš„å­èŠ‚ç‚¹
            val.children = children;
@@ -775,8 +775,8 @@
      if (valid) {
        // æž„建提交参数
        const params = {
          materialId: currentId.value,
          specification: modelForm.value.specification,
          productId: currentId.value,
          model: modelForm.value.model,
          materialCode: modelForm.value.materialCode,
          supplyType: modelForm.value.supplyType,
        };
@@ -821,10 +821,10 @@
    }
    tableLoading.value = true;
    modelListPage({
      materialId: currentId.value,
      productId: currentId.value,
      current: page.current,
      size: page.size,
      specification: specification.value,
      model: model.value,
      materialCode: materialCode.value,
    }).then(res => {
      console.log("res", res);
@@ -837,7 +837,7 @@
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.skuId);
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
src/views/inventoryManagement/stockManagement/New.vue
@@ -1,71 +1,67 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增库存"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
    <el-dialog v-model="isShow"
               title="新增库存"
               width="800"
               @close="closeModal">
      <el-form label-width="140px"
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品名称"
                      prop="productModelId"
                      :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            ]">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        <el-form-item label="规格"
                      prop="productModelName">
          <el-input v-model="formState.productModelName"
                    disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formState.unit"
                    disabled />
        </el-form-item>
        <el-form-item
            label="库存数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="1" style="width: 100%" />
        <el-form-item label="库存数量"
                      prop="qualitity">
          <el-input-number v-model="formState.qualitity"
                           :step="1"
                           :min="1"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item
            v-if="type === 'qualified'"
            label="库存预警数量"
            prop="warnNum"
        >
          <el-input-number v-model="formState.warnNum" :step="1" :min="0" :max="formState.qualitity" style="width: 100%" />
        <el-form-item v-if="type === 'qualified'"
                      label="库存预警数量"
                      prop="warnNum">
          <el-input-number v-model="formState.warnNum"
                           :step="1"
                           :min="0"
                           :max="formState.qualitity"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formState.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <ProductSelectDialog v-model="showProductSelectDialog"
                           @confirm="handleProductSelect"
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
@@ -74,116 +70,115 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {createStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {createStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
  import { ref, computed, getCurrentInstance } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { createStockInventory } from "@/api/inventoryManagement/stockInventory.js";
  import { createStockUnInventory } from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
    },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
});
    type: {
      type: String,
      required: true,
      default: "qualified",
    },
  });
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  qualitity: 0,
  warnNum: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
  // å“åº”式数据(替代选项式的 data)
  const formState = ref({
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
    unit: "",
    qualitity: 0,
    warnNum: 0,
    remark: "",
  });
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
  const showProductSelectDialog = ref(false);
  let { proxy } = getCurrentInstance();
  const closeModal = () => {
    // é‡ç½®è¡¨å•数据
    formState.value = {
      productId: undefined,
      productModelId: undefined,
      productName: "",
      productModelName: "",
      description: "",
    };
    isShow.value = false;
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
  // äº§å“é€‰æ‹©å¤„理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      formState.value.productId = product.id;
      formState.value.productName = product.productName;
      formState.value.productModelName = product.model;
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
        createStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        createStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      }
      formState.value.productModelId = product.id;
      formState.value.unit = product.unit;
      showProductSelectDialog.value = false;
      // è§¦å‘表单验证更新
      proxy.$refs["formRef"]?.validateField("productModelId");
    }
  })
};
  };
  const handleSubmit = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择产品");
          return;
        }
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择规格");
          return;
        }
        if (props.type === "qualified") {
          createStockInventory(formState.value).then(res => {
            // å…³é—­æ¨¡æ€æ¡†
            isShow.value = false;
            // å‘ŠçŸ¥çˆ¶ç»„件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        } else {
          createStockUnInventory(formState.value).then(res => {
            // å…³é—­æ¨¡æ€æ¡†
            isShow.value = false;
            // å‘ŠçŸ¥çˆ¶ç»„件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        }
      }
    });
  };
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
  defineExpose({
    closeModal,
    handleSubmit,
    isShow,
  });
</script>
src/views/inventoryManagement/stockManagement/Subtract.vue
@@ -1,63 +1,60 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="领用"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
    <el-dialog v-model="isShow"
               title="领用"
               width="800"
               @close="closeModal">
      <el-form label-width="140px"
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品名称"
                      prop="productModelId"
                      :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true" disabled>
            ]">
          <el-button type="primary"
                     @click="showProductSelectDialog = true"
                     disabled>
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.model"  disabled />
        <el-form-item label="规格"
                      prop="productModelName">
          <el-input v-model="formState.model"
                    disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formState.unit"
                    disabled />
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="1" :max="maxQuality" style="width: 100%" />
        <el-form-item label="数量"
                      prop="qualitity">
          <el-input-number v-model="formState.qualitity"
                           :step="1"
                           :min="1"
                           :max="maxQuality"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formState.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <ProductSelectDialog v-model="showProductSelectDialog"
                           @confirm="handleProductSelect"
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
@@ -66,134 +63,133 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {subtractStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {subtractStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
  import { ref, computed, getCurrentInstance } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { subtractStockInventory } from "@/api/inventoryManagement/stockInventory.js";
  import { subtractStockUnInventory } from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    default: () => {},
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
});
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
    },
    record: {
      type: Object,
      default: () => {},
    },
    type: {
      type: String,
      required: true,
      default: "qualified",
    },
  });
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
onMounted(() => {
  initFormData()
})
  onMounted(() => {
    initFormData();
  });
const maxQuality = computed(() => {
  return props.record.unLockedQuantity ? props.record.unLockedQuantity :  0;
})
  const maxQuality = computed(() => {
    return props.record.unLockedQuantity ? props.record.unLockedQuantity : 0;
  });
const initFormData = () => {
  if (props.record) {
    formState.value = {
      ...props.record,
  const initFormData = () => {
    if (props.record) {
      formState.value = {
        ...props.record,
      };
    }
  }
}
  };
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  model: "",
  unit: "",
  qualitity: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
  // å“åº”式数据(替代选项式的 data)
  const formState = ref({
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    description: '',
    model: "",
    unit: "",
    qualitity: 0,
    remark: "",
  });
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
  const showProductSelectDialog = ref(false);
  let { proxy } = getCurrentInstance();
  const closeModal = () => {
    // é‡ç½®è¡¨å•数据
    formState.value = {
      productId: undefined,
      productModelId: undefined,
      productName: "",
      productModelName: "",
      description: "",
    };
    isShow.value = false;
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    console.log(product)
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
        subtractStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        subtractStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      }
  // äº§å“é€‰æ‹©å¤„理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      console.log(product);
      formState.value.productId = product.id;
      formState.value.productName = product.productName;
      formState.value.productModelName = product.model;
      formState.value.productModelId = product.id;
      formState.value.unit = product.unit;
      showProductSelectDialog.value = false;
      // è§¦å‘表单验证更新
      proxy.$refs["formRef"]?.validateField("productModelId");
    }
  })
};
  };
  const handleSubmit = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择产品");
          return;
        }
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择规格");
          return;
        }
        if (props.type === "qualified") {
          subtractStockInventory(formState.value).then(res => {
            // å…³é—­æ¨¡æ€æ¡†
            isShow.value = false;
            // å‘ŠçŸ¥çˆ¶ç»„件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        } else {
          subtractStockUnInventory(formState.value).then(res => {
            // å…³é—­æ¨¡æ€æ¡†
            isShow.value = false;
            // å‘ŠçŸ¥çˆ¶ç»„件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        }
      }
    });
  };
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
  defineExpose({
    closeModal,
    handleSubmit,
    isShow,
  });
</script>
src/views/productionManagement/processRoute/Edit.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/ItemsForm.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/New.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/index.vue
@@ -1,204 +1,1573 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="规格名称:">
          <el-input v-model="searchForm.model" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
    <div class="route-header">
      <div class="add-route-btn"
           @click="handleAddRoute">
        <el-icon>
          <Plus />
        </el-icon>
        <span>新增工艺路线</span>
      </div>
    </div>
    <div class="route-card-list">
      <div v-for="route in routeList"
           :key="route.id"
           class="route-card">
        <div class="card-header">
          <div class="route-info">
            <span class="route-name"><el-icon style="margin-right: 8px;line-height: 30px;">
                <ScaleToOriginal />
              </el-icon>{{ route.routeName }}<el-tag style="margin-left: 8px"
                      :type="route.status == 1 ? 'warning' : 'success'">{{ route.status == 1 ? '草稿' : '批准' }}</el-tag></span>
            <span class="route-code">{{ route.routeCode }}</span>
          </div>
          <div class="route-actions">
            <el-button v-if="route.status === '1'"
                       link
                       type="success"
                       @click="handleApproveRoute(route)">
              <el-icon>
                <Check />
              </el-icon>
              æ‰¹å‡†
            </el-button>
            <el-button v-if="route.status === '2'"
                       link
                       type="warning"
                       @click="handleRevokeApproveRoute(route)">
              <el-icon>
                <Close />
              </el-icon>
              æ’¤é”€æ‰¹å‡†
            </el-button>
            <el-button link
                       type="primary"
                       @click="handleEditRoute(route)">
              <el-icon>
                <Edit />
              </el-icon>
              ç¼–辑
            </el-button>
            <el-button link
                       type="danger"
                       @click="handleDeleteRoute(route)">
              <el-icon>
                <Delete />
              </el-icon>
              åˆ é™¤
            </el-button>
          </div>
        </div>
        <div class="card-body">
          <div class="route-meta">
            <span class="meta-item">
              <el-icon>
                <Box />
              </el-icon>
              <span class="meta-label">产品:</span>
              <span class="meta-value">{{ route.productName }} - {{ route.productModelName }}</span>
            </span>
            <span class="meta-item">
              <el-icon>
                <Document />
              </el-icon>
              <span class="meta-label">BOM:</span>
              <span class="meta-value">{{ route.bomId || '-' }}</span>
            </span>
            <span class="meta-item">
              <el-icon>
                <Document />
              </el-icon>
              <span class="meta-label">路线描述:</span>
              <span class="meta-value">{{ route.routeDesc || '暂无描述' }}</span>
            </span>
          </div>
          <div class="expand-btn-wrapper">
            <el-button class="expand-btn"
                       :class="{ expanded: route.expanded }"
                       type="primary"
                       text
                       @click="toggleExpand(route)">
              <span class="btn-text">{{ route.expanded ? '收起工序路线' : '展开工序路线' }}</span>
              <el-icon class="expand-icon">
                <component :is="route.expanded ? 'ArrowUp' : 'ArrowDown'" />
              </el-icon>
            </el-button>
          </div>
        </div>
        <div v-if="route.expanded"
             class="process-route">
          <div class="process-flow">
            <div v-for="(process, index) in route.processList"
                 :key="process.id"
                 class="process-flow-item"
                 draggable="true"
                 @dragstart="handleDragStart($event, index, route.id)"
                 @dragover="handleDragOver($event)"
                 @drop="handleDrop($event, index, route.id)"
                 @dragend="handleDragEnd">
              <div class="process-node"
                   :class="{ expanded: process.expanded }">
                <div class="process-node-header">
                  <div class="process-number">{{ index + 1 }}</div>
                  <div class="process-actions">
                    <el-button link
                               type="danger"
                               @click="handleDeleteProcess(route.id, process)">
                      <el-icon>
                        <Delete />
                      </el-icon>
                    </el-button>
                  </div>
                </div>
                <div class="process-node-body">
                  <div class="process-code">{{ process.processCode }}</div>
                  <div class="process-name">{{ process.processName }}</div>
                  <div class="process-desc">{{ process.processDesc || '暂无描述' }}</div>
                </div>
                <div class="process-node-footer">
                  <!-- <el-tag size="small"
                          :type="process.status === '1' ? 'success' : 'info'">
                    {{ process.status === '1' ? '启用' : '停用' }}
                  </el-tag> -->
                  <el-button type="primary"
                             link
                             size="small"
                             @click="toggleProcessParams(process)">
                    {{ process.expanded ? '收起参数' : '展开参数' }}
                    ({{ process.paramList?.length || 0 }})
                  </el-button>
                </div>
                <div v-if="process.expanded"
                     class="process-params-section">
                  <div class="params-header">
                    <span>参数列表</span>
                    <el-button type="primary"
                               link
                               size="small"
                               @click="handleAddParam(route.id, process)">
                      <el-icon>
                        <Plus />
                      </el-icon>新增
                    </el-button>
                  </div>
                  <div class="params-list">
                    <div v-for="param in process.paramList"
                         :key="param.id"
                         class="param-item">
                      <div class="param-info">
                        <span class="param-code">{{ param.parameterCode }}</span>
                        <span class="param-name">{{ param.parameterName }}</span>
                        <!-- <el-tag size="small"
                                style="margin-right: 20px;"
                                :type="getParamTypeTag(param.parameterType)">
                          {{ param.parameterType }}
                        </el-tag> -->
                        <span class="param-value">标准值:{{ param.standardValue }} {{ param.unit }}</span>
                      </div>
                      <div class="param-actions">
                        <el-button link
                                   type="primary"
                                   size="small"
                                   @click="handleEditParam(route.id, process, param)">
                          ç¼–辑
                        </el-button>
                        <el-button link
                                   type="danger"
                                   size="small"
                                   @click="handleDeleteParam(route.id, process, param)">
                          åˆ é™¤
                        </el-button>
                      </div>
                    </div>
                    <el-empty v-if="!process.paramList || process.paramList.length === 0"
                              description="暂无参数"
                              :image-size="50" />
                  </div>
                </div>
              </div>
              <div v-if="index < route.processList.length - 1"
                   class="flow-arrow">
                <el-icon>
                  <Right />
                </el-icon>
              </div>
            </div>
            <div class="add-process-node"
                 @click="handleSelectProcess(route, index)">
              <el-icon>
                <Plus />
              </el-icon>
              <span>新增工序</span>
            </div>
          </div>
          <el-empty v-if="!route.processList || route.processList.length === 0"
                    description="暂无工序"
                    :image-size="80" />
        </div>
      </div>
    </div>
    <!-- å·¥è‰ºè·¯çº¿æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="routeDialogVisible"
               :title="isRouteEdit ? '编辑工艺路线' : '新增工艺路线'"
               width="500px">
      <el-form :model="routeForm"
               :rules="routeRules"
               ref="routeFormRef"
               label-width="120px">
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ routeForm.productName && routeForm.productModelName
              ? `${routeForm.productName} - ${routeForm.productModelName}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
        <el-form-item label="BOM"
                      prop="bomId">
          <el-select v-model="routeForm.bomId"
                     placeholder="请选择BOM"
                     clearable
                     :disabled="!routeForm.productModelId || bomOptions.length === 0"
                     style="width: 100%">
            <el-option v-for="item in bomOptions"
                       :key="item.id"
                       :label="item.bomNo || `BOM-${item.id}`"
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="路线编码"
                      prop="routeCode">
          <el-input v-model="routeForm.routeCode"
                    placeholder="请输入路线编码" />
        </el-form-item>
        <el-form-item label="路线名称"
                      prop="routeName">
          <el-input v-model="routeForm.routeName"
                    placeholder="请输入路线名称" />
        </el-form-item>
        <el-form-item label="路线描述"
                      prop="routeDesc">
          <el-input v-model="routeForm.routeDesc"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入路线描述" />
        </el-form-item>
        <!-- <el-form-item label="状态"
                      prop="status">
          <el-radio-group v-model="routeForm.status">
            <el-radio label="1">启用</el-radio>
            <el-radio label="0">停用</el-radio>
          </el-radio-group>
        </el-form-item> -->
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="routeDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleRouteSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¼¹çª— -->
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- å·¥åºæ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="processDialogVisible"
               :title="isProcessEdit ? '编辑工序' : '新增工序'"
               width="500px">
      <el-form :model="processForm"
               :rules="processRules"
               ref="processFormRef"
               label-width="120px">
        <el-form-item label="工序编码"
                      prop="processCode">
          <el-input v-model="processForm.processCode"
                    placeholder="请输入工序编码" />
        </el-form-item>
        <el-form-item label="工序名称"
                      prop="processName">
          <el-input v-model="processForm.processName"
                    placeholder="请输入工序名称" />
        </el-form-item>
        <el-form-item label="工序描述"
                      prop="processDesc">
          <el-input v-model="processForm.processDesc"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入工序描述" />
        </el-form-item>
        <el-form-item label="状态"
                      prop="status">
          <el-radio-group v-model="processForm.status">
            <el-radio label="1">启用</el-radio>
            <el-radio label="0">停用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div style="text-align: right" class="mb10">
        <el-button type="primary" @click="showNewModal">新增工艺路线</el-button>
        <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>删除工艺路线</el-button>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="processDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleProcessSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- é€‰æ‹©å·¥åºå¯¹è¯æ¡† -->
    <el-dialog v-model="selectProcessDialogVisible"
               title="选择工序"
               width="1000px">
      <div class="process-select-container">
        <!-- å·¦ä¾§å·¥åºåˆ—表 -->
        <div class="process-list-area">
          <div class="area-title">可选工序</div>
          <div class="search-box">
            <el-input v-model="processSearchKeyword"
                      placeholder="请输入工序名称搜索"
                      clearable
                      size="small"
                      @input="handleProcessSearch">
              <template #prefix>
                <el-icon>
                  <Search />
                </el-icon>
              </template>
            </el-input>
          </div>
          <el-table :data="filteredProcessList"
                    height="360"
                    border
                    highlight-current-row
                    @current-change="handleProcessSelect">
            <el-table-column prop="processCode"
                             label="工序编号"
                             width="100" />
            <el-table-column prop="processName"
                             label="工序名称" />
            <el-table-column prop="processDesc"
                             label="工序描述" />
            <el-table-column prop="status"
                             label="状态"
                             width="80">
              <template #default="scope">
                <el-tag size="small"
                        :type="scope.row.status === '1' ? 'success' : 'info'">
                  {{ scope.row.status === '1' ? '启用' : '停用' }}
                </el-tag>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <!-- å³ä¾§å·¥åºè¯¦æƒ… -->
        <div class="process-detail-area">
          <div class="area-title">工序详情</div>
          <el-form v-if="selectedProcessItem"
                   :model="selectedProcessItem"
                   label-width="100px"
                   class="process-detail-form">
            <el-form-item label="工序编号">
              <span class="detail-text">{{ selectedProcessItem.processCode }}</span>
            </el-form-item>
            <el-form-item label="工序名称">
              <span class="detail-text">{{ selectedProcessItem.processName }}</span>
            </el-form-item>
            <el-form-item label="工序描述">
              <span class="detail-text">{{ selectedProcessItem.processDesc || '-' }}</span>
            </el-form-item>
            <el-form-item label="状态">
              <el-tag size="small"
                      :type="selectedProcessItem.status === '1' ? 'success' : 'info'">
                {{ selectedProcessItem.status === '1' ? '启用' : '停用' }}
              </el-tag>
            </el-form-item>
            <el-form-item label="参数数量">
              <span class="detail-text">{{ selectedProcessItem.paramCount || 0 }}个</span>
            </el-form-item>
          </el-form>
          <el-empty v-else
                    description="请从左侧选择工序" />
        </div>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      />
    </div>
    <new-process
        v-if="isShowNewModal"
        v-model:visible="isShowNewModal"
        @completed="getList"
    />
    <edit-process
        v-if="isShowEditModal"
        v-model:visible="isShowEditModal"
        :record="record"
        @completed="getList"
    />
    <route-item-form
        v-if="isShowItemModal"
        v-model:visible="isShowItemModal"
        :record="record"
        @completed="getList"
    />
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="selectProcessDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     :disabled="!selectedProcessItem"
                     @click="handleProcessSelectSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- å‚数新增/编辑对话框 -->
    <el-dialog v-model="paramDialogVisible"
               :title="isParamEdit ? '编辑参数' : '新增参数'"
               width="500px">
      <el-form :model="paramForm"
               :rules="paramRules"
               ref="paramFormRef"
               label-width="120px">
        <el-form-item label="参数编号"
                      prop="parameterCode">
          <el-input v-model="paramForm.parameterCode"
                    placeholder="请输入参数编号" />
        </el-form-item>
        <el-form-item label="参数名称"
                      prop="parameterName">
          <el-input v-model="paramForm.parameterName"
                    placeholder="请输入参数名称" />
        </el-form-item>
        <el-form-item label="参数模式"
                      prop="parameterType2">
          <el-select v-model="paramForm.parameterType2"
                     placeholder="请选择参数模式">
            <el-option label="单值"
                       value="1" />
            <el-option label="区间"
                       value="2" />
          </el-select>
        </el-form-item>
        <el-form-item label="参数类型"
                      prop="parameterType">
          <el-select v-model="paramForm.parameterType"
                     @change="handleParamTypeChange"
                     placeholder="请选择参数类型">
            <el-option label="数值格式"
                       value="数值格式" />
            <el-option label="文本格式"
                       value="文本格式" />
            <el-option label="下拉选项"
                       value="下拉选项" />
            <el-option label="时间格式"
                       value="时间格式" />
          </el-select>
        </el-form-item>
        <el-form-item v-if="paramForm.parameterType === '下拉选项'"
                      label="数据字典"
                      prop="parameterFormat">
          <el-select v-model="paramForm.parameterFormat"
                     placeholder="请选择数据字典">
            <el-option v-for="item in dictTypes"
                       :key="item.dictType"
                       :label="item.dictName"
                       :value="item.dictType" />
          </el-select>
        </el-form-item>
        <el-form-item v-else-if="paramForm.parameterType === '时间格式'"
                      label="时间格式"
                      prop="parameterFormat">
          <el-select v-model="paramForm.parameterFormat"
                     placeholder="请选择时间格式">
            <el-option label="YYYY-MM-DD HH:mm:ss"
                       value="YYYY-MM-DD HH:mm:ss" />
            <el-option label="YYYY-MM-DD"
                       value="YYYY-MM-DD" />
          </el-select>
        </el-form-item>
        <el-form-item v-else
                      label="参数格式"
                      prop="parameterFormat">
          <el-input v-model="paramForm.parameterFormat"
                    placeholder="请输入参数格式" />
        </el-form-item>
        <el-form-item label="标准值"
                      prop="standardValue">
          <el-input v-model="paramForm.standardValue"
                    placeholder="请输入标准值" />
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="paramForm.unit"
                    placeholder="请输入单位" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="paramDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleParamSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import NewProcess from "@/views/productionManagement/processRoute/New.vue";
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import {listPage, del} from "@/api/productionManagement/processRoute.js";
import { useRouter } from 'vue-router'
  import { ref, reactive, getCurrentInstance } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import {
    Plus,
    Edit,
    Delete,
    ArrowUp,
    ArrowDown,
    Right,
    Search,
    Check,
    Close,
    Box,
    Document,
  } from "@element-plus/icons-vue";
  import { listType } from "@/api/system/dict/type";
  import { getByModel } from "@/api/productionManagement/productBom.js";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const router = useRouter()
const data = reactive({
  searchForm: {
    model: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "工艺路线编号",
    prop: "processRouteCode",
  },
  {
    label: "产品名称",
    prop: "productName",
  },
  {
    label: "规格名称",
    prop: "model",
  },
  {
    label: "BOM编号",
    prop: "bomNo",
  },
  {
    label: "描述",
    prop: "description",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 280,
    operation: [
  // å·¥è‰ºè·¯çº¿åˆ—表
  const routeList = ref([]);
  const dictTypes = ref([]);
  // èŽ·å–å…¨å±€å®žä¾‹
  const { proxy } = getCurrentInstance();
  // äº§å“é€‰æ‹©å’ŒBOM相关
  const showProductSelectDialog = ref(false);
  const bomOptions = ref([]);
  // å·¥è‰ºè·¯çº¿å¯¹è¯æ¡†
  const routeDialogVisible = ref(false);
  const isRouteEdit = ref(false);
  const routeFormRef = ref(null);
  const routeForm = reactive({
    id: null,
    productModelId: null,
    productName: "",
    productModelName: "",
    bomId: null,
    routeCode: "",
    routeName: "",
    routeDesc: "",
    status: "1",
  });
  const routeRules = {
    productModelId: [
      { required: true, message: "请选择产品", trigger: "change" },
    ],
    bomId: [{ required: true, message: "请选择BOM", trigger: "change" }],
    routeCode: [{ required: true, message: "请输入路线编码", trigger: "blur" }],
    routeName: [{ required: true, message: "请输入路线名称", trigger: "blur" }],
  };
  // å·¥åºå¯¹è¯æ¡†
  const processDialogVisible = ref(false);
  const isProcessEdit = ref(false);
  const processFormRef = ref(null);
  const currentRouteId = ref(null);
  const processForm = reactive({
    id: null,
    processCode: "",
    processName: "",
    processDesc: "",
    status: "1",
  });
  const processRules = {
    processCode: [{ required: true, message: "请输入工序编码", trigger: "blur" }],
    processName: [{ required: true, message: "请输入工序名称", trigger: "blur" }],
  };
  // é€‰æ‹©å·¥åºå¯¹è¯æ¡†
  const selectProcessDialogVisible = ref(false);
  const availableProcessList = ref([]);
  const filteredProcessList = ref([]);
  const selectedProcessItem = ref(null);
  const processSearchKeyword = ref("");
  const currentRouteIndex = ref(null);
  // å‚数对话框
  const paramDialogVisible = ref(false);
  const isParamEdit = ref(false);
  const paramFormRef = ref(null);
  const currentProcessId = ref(null);
  const paramForm = reactive({
    id: null,
    parameterCode: "",
    parameterName: "",
    parameterType2: "1",
    parameterType: "",
    parameterFormat: "",
    standardValue: "",
    unit: "",
  });
  const paramRules = {
    parameterCode: [
      { required: true, message: "请输入参数编号", trigger: "blur" },
    ],
    parameterName: [
      { required: true, message: "请输入参数名称", trigger: "blur" },
    ],
    parameterType: [
      { required: true, message: "请选择参数类型", trigger: "change" },
    ],
  };
  // æ‹–拽相关
  const draggedItem = ref(null);
  const draggedRouteId = ref(null);
  // èŽ·å–å·¥è‰ºè·¯çº¿åˆ—è¡¨
  const getRouteList = () => {
    routeList.value = [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          showEditModal(row);
        }
        id: 1,
        productModelId: 1,
        productName: "标准砌块",
        productModelName: "3.5型",
        bomId: 1,
        routeCode: "ROUTE001",
        routeName: "标准砌块生产线",
        routeDesc: "标准砌块生产流程",
        status: "1",
        expanded: false,
        processList: [
          {
            id: 1,
            processCode: "PROC001",
            processName: "原料配比",
            processDesc: "原材料配比工序",
            status: "1",
            expanded: false,
            paramList: [
              {
                id: 1,
                parameterCode: "P001",
                parameterName: "水泥比例",
                parameterType2: "1",
                parameterType: "数值格式",
                parameterFormat: "",
                standardValue: "30",
                unit: "%",
              },
              {
                id: 2,
                parameterCode: "P002",
                parameterName: "砂比例",
                parameterType2: "1",
                parameterType: "数值格式",
                parameterFormat: "",
                standardValue: "60",
                unit: "%",
              },
            ],
          },
          {
            id: 2,
            processCode: "PROC002",
            processName: "搅拌混合",
            processDesc: "搅拌混合工序",
            status: "1",
            expanded: false,
            paramList: [
              {
                id: 3,
                parameterCode: "P003",
                parameterName: "搅拌时间",
                parameterType2: "1",
                parameterType: "数值格式",
                parameterFormat: "",
                standardValue: "5",
                unit: "分钟",
              },
            ],
          },
          {
            id: 3,
            processCode: "PROC003",
            processName: "浇筑成型",
            processDesc: "浇筑成型工序",
            status: "1",
            expanded: false,
            paramList: [],
          },
        ],
      },
      {
        name: "路线项目",
        type: "text",
        clickFun: (row) => {
          showItemModal(row);
        }
        id: 2,
        productModelId: 2,
        productName: "板材",
        productModelName: "5.0型",
        bomId: 2,
        routeCode: "ROUTE002",
        routeName: "板材生产线",
        routeDesc: "板材生产流程",
        status: "1",
        expanded: false,
        processList: [
          {
            id: 4,
            processCode: "PROC004",
            processName: "切割加工",
            processDesc: "切割加工工序",
            status: "1",
            expanded: false,
            paramList: [
              {
                id: 4,
                parameterCode: "P004",
                parameterName: "切割尺寸",
                parameterType2: "1",
                parameterType: "文本格式",
                parameterFormat: "",
                standardValue: "600x200x100",
                unit: "mm",
              },
            ],
          },
        ],
      },
    ];
  };
  // å±•å¼€/收起工艺路线
  const toggleExpand = route => {
    route.expanded = !route.expanded;
  };
  // å±•å¼€/收起工序参数
  const toggleProcessParams = process => {
    process.expanded = !process.expanded;
  };
  // å·¥è‰ºè·¯çº¿æ“ä½œ
  const handleAddRoute = () => {
    isRouteEdit.value = false;
    routeForm.id = null;
    routeForm.productModelId = null;
    routeForm.productName = "";
    routeForm.productModelName = "";
    routeForm.bomId = null;
    routeForm.routeCode = "";
    routeForm.routeName = "";
    routeForm.routeDesc = "";
    routeForm.status = "1";
    bomOptions.value = [];
    routeDialogVisible.value = true;
  };
  const handleEditRoute = route => {
    isRouteEdit.value = true;
    routeForm.id = route.id;
    routeForm.productModelId = route.productModelId;
    routeForm.productName = route.productName;
    routeForm.productModelName = route.productModelName;
    routeForm.bomId = route.bomId;
    routeForm.routeCode = route.routeCode;
    routeForm.routeName = route.routeName;
    routeForm.routeDesc = route.routeDesc;
    routeForm.status = route.status;
    routeDialogVisible.value = true;
  };
  const handleDeleteRoute = route => {
    ElMessageBox.confirm("确定要删除该工艺路线吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      ElMessage.success("删除成功");
      getRouteList();
    });
  };
  const handleRouteSubmit = () => {
    routeFormRef.value.validate(valid => {
      if (valid) {
        ElMessage.success(isRouteEdit.value ? "编辑成功" : "新增成功");
        routeDialogVisible.value = false;
        getRouteList();
      }
    ]
  }
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const isShowItemModal = ref(false);
const record = ref({});
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const { proxy } = getCurrentInstance()
    });
  };
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
  // äº§å“é€‰æ‹©å¤„理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      // å…ˆæŸ¥è¯¢BOM列表(必选)
      try {
        const res = await getByModel(product.id);
        // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
        let bomList = [];
        if (Array.isArray(res)) {
          bomList = res;
        } else if (res && res.data) {
          bomList = Array.isArray(res.data) ? res.data : [res.data];
        } else if (res && typeof res === "object") {
          bomList = [res];
        }
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  params.entryDate = undefined
  listPage(params).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records.map(item => ({
      ...item,
    }));
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开新增弹框
const showNewModal = () => {
  isShowNewModal.value = true
};
const showEditModal = (row) => {
  isShowEditModal.value = true
  record.value = row
};
const showItemModal = (row) => {
  router.push({
    path: '/productionManagement/processRouteItem',
    query: {
      id: row.id,
      processRouteCode: row.processRouteCode || '',
      productName: row.productName || '',
      model: row.model || '',
      bomNo: row.bomNo || '',
      description: row.description || '',
      type: 'route',
        if (bomList.length > 0) {
          routeForm.productModelId = product.id;
          routeForm.productName = product.productName;
          routeForm.productModelName = product.model;
          routeForm.bomId = undefined; // é‡ç½®BOM选择
          bomOptions.value = bomList;
          showProductSelectDialog.value = false;
          // è§¦å‘表单验证更新
          proxy.$refs["routeFormRef"]?.validateField("productModelId");
        } else {
          proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
        }
      } catch (error) {
        // å¦‚果接口返回404或其他错误,说明没有BOM
        proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
      }
    }
  })
};
  };
// åˆ é™¤
function handleDelete() {
  const ids = selectedRows.value.map((item) => item.id);
  proxy.$modal
      .confirm('是否确认删除已勾选的数据项?')
      .then(function () {
        return del(ids);
      })
      .then(() => {
        getList();
        proxy.$modal.msgSuccess("删除成功");
      })
      .catch(() => {});
}
  const handleApproveRoute = route => {
    ElMessageBox.confirm("确定要批准该工艺路线吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "info",
    }).then(() => {
      route.status = "2";
      ElMessage.success("批准成功");
    });
  };
onMounted(() => {
  getList();
});
  const handleRevokeApproveRoute = route => {
    ElMessageBox.confirm("确定要撤销批准该工艺路线吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      route.status = "1";
      ElMessage.success("撤销批准成功");
    });
  };
  // å·¥åºæ“ä½œ
  const handleSelectProcess = (route, index) => {
    currentRouteId.value = route.id;
    currentRouteIndex.value = index;
    // èŽ·å–å¯é€‰å·¥åºåˆ—è¡¨ï¼ˆå‡æ•°æ®ï¼‰
    availableProcessList.value = [
      {
        id: 1,
        processCode: "PROC001",
        processName: "原料配比",
        processDesc: "原材料配比工序",
        status: "1",
        paramCount: 3,
      },
      {
        id: 2,
        processCode: "PROC002",
        processName: "搅拌混合",
        processDesc: "搅拌混合工序",
        status: "1",
        paramCount: 2,
      },
      {
        id: 3,
        processCode: "PROC003",
        processName: "浇筑成型",
        processDesc: "浇筑成型工序",
        status: "1",
        paramCount: 4,
      },
      {
        id: 4,
        processCode: "PROC004",
        processName: "蒸压养护",
        processDesc: "蒸压养护工序",
        status: "0",
        paramCount: 2,
      },
      {
        id: 5,
        processCode: "PROC005",
        processName: "切割加工",
        processDesc: "切割加工工序",
        status: "1",
        paramCount: 3,
      },
    ];
    filteredProcessList.value = availableProcessList.value;
    processSearchKeyword.value = "";
    selectedProcessItem.value = null;
    selectProcessDialogVisible.value = true;
  };
  const handleEditProcess = (routeId, process) => {
    currentRouteId.value = routeId;
    isProcessEdit.value = true;
    processForm.id = process.id;
    processForm.processCode = process.processCode;
    processForm.processName = process.processName;
    processForm.processDesc = process.processDesc;
    processForm.status = process.status;
    processDialogVisible.value = true;
  };
  const handleDeleteProcess = (routeId, process) => {
    ElMessageBox.confirm("确定要删除该工序吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      ElMessage.success("删除成功");
      getRouteList();
    });
  };
  const handleProcessSubmit = () => {
    processFormRef.value.validate(valid => {
      if (valid) {
        ElMessage.success(isProcessEdit.value ? "编辑成功" : "新增成功");
        processDialogVisible.value = false;
        getRouteList();
      }
    });
  };
  // é€‰æ‹©å·¥åºç›¸å…³æ–¹æ³•
  const handleProcessSearch = () => {
    const keyword = processSearchKeyword.value.trim().toLowerCase();
    if (!keyword) {
      filteredProcessList.value = availableProcessList.value;
    } else {
      filteredProcessList.value = availableProcessList.value.filter(item =>
        item.processName.toLowerCase().includes(keyword)
      );
    }
  };
  const handleProcessSelect = row => {
    selectedProcessItem.value = row;
  };
  const handleProcessSelectSubmit = () => {
    if (!selectedProcessItem.value) {
      ElMessage.warning("请先选择一个工序");
      return;
    }
    // æ£€æŸ¥å·¥åºæ˜¯å¦å·²å­˜åœ¨
    const route = routeList.value[currentRouteIndex.value];
    const exists = route.processList.some(
      p => p.id === selectedProcessItem.value.id
    );
    if (exists) {
      ElMessage.warning("该工序已存在于工艺路线中");
      return;
    }
    // æ·»åŠ å·¥åºåˆ°å·¥è‰ºè·¯çº¿
    const newProcess = {
      id: Date.now(),
      processCode: selectedProcessItem.value.processCode,
      processName: selectedProcessItem.value.processName,
      processDesc: selectedProcessItem.value.processDesc,
      status: selectedProcessItem.value.status,
      paramList: [],
      expanded: false,
    };
    route.processList.push(newProcess);
    ElMessage.success("添加工序成功");
    selectProcessDialogVisible.value = false;
  };
  // å‚数操作
  const handleAddParam = (routeId, process) => {
    currentRouteId.value = routeId;
    currentProcessId.value = process.id;
    isParamEdit.value = false;
    paramForm.id = null;
    paramForm.parameterCode = "";
    paramForm.parameterName = "";
    paramForm.parameterType2 = "1";
    paramForm.parameterType = "";
    paramForm.parameterFormat = "";
    paramForm.standardValue = "";
    paramForm.unit = "";
    paramDialogVisible.value = true;
  };
  const handleEditParam = (routeId, process, param) => {
    currentRouteId.value = routeId;
    currentProcessId.value = process.id;
    isParamEdit.value = true;
    paramForm.id = param.id;
    paramForm.parameterCode = param.parameterCode;
    paramForm.parameterName = param.parameterName;
    paramForm.parameterType2 = param.parameterType2 || "1";
    paramForm.parameterType = param.parameterType;
    paramForm.parameterFormat = param.parameterFormat;
    paramForm.standardValue = param.standardValue;
    paramForm.unit = param.unit;
    paramDialogVisible.value = true;
  };
  const handleDeleteParam = (routeId, process, param) => {
    ElMessageBox.confirm("确定要删除该参数吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      ElMessage.success("删除成功");
      getRouteList();
    });
  };
  const handleParamSubmit = () => {
    paramFormRef.value.validate(valid => {
      if (valid) {
        ElMessage.success(isParamEdit.value ? "编辑成功" : "新增成功");
        paramDialogVisible.value = false;
        getRouteList();
      }
    });
  };
  const handleParamTypeChange = () => {
    if (paramForm.parameterType === "数值格式") {
      paramForm.parameterFormat = "#.0000";
    } else if (paramForm.parameterType === "时间格式") {
      paramForm.parameterFormat = "YYYY-MM-DD HH:mm:ss";
    } else {
      paramForm.parameterFormat = "";
    }
  };
  const getParamTypeTag = type => {
    const typeMap = {
      æ•°å€¼æ ¼å¼: "primary",
      æ–‡æœ¬æ ¼å¼: "info",
      ä¸‹æ‹‰é€‰é¡¹: "warning",
      æ—¶é—´æ ¼å¼: "success",
    };
    return typeMap[type] || "default";
  };
  // æ‹–拽排序
  const handleDragStart = (event, index, routeId) => {
    draggedItem.value = index;
    draggedRouteId.value = routeId;
    event.dataTransfer.effectAllowed = "move";
  };
  const handleDragOver = event => {
    event.preventDefault();
    event.dataTransfer.dropEffect = "move";
  };
  const handleDrop = (event, dropIndex, routeId) => {
    event.preventDefault();
    if (draggedItem.value === null || draggedItem.value === dropIndex) return;
    const route = routeList.value.find(r => r.id === routeId);
    if (route && route.processList) {
      const draggedProcess = route.processList[draggedItem.value];
      route.processList.splice(draggedItem.value, 1);
      route.processList.splice(dropIndex, 0, draggedProcess);
      ElMessage.success("排序成功");
    }
  };
  const handleDragEnd = () => {
    draggedItem.value = null;
    draggedRouteId.value = null;
  };
  // èŽ·å–æ•°æ®å­—å…¸
  const getDictTypes = () => {
    listType({ pageNum: 1, pageSize: 1000 }).then(res => {
      dictTypes.value = res.rows || [];
    });
  };
  getRouteList();
  getDictTypes();
</script>
<style scoped></style>
<style scoped lang="scss">
  .app-container {
    padding: 20px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 84px);
  }
  .route-header {
    margin-bottom: 20px;
    .add-route-btn {
      width: 100%;
      display: inline-flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-width: 120px;
      height: 100px;
      border: 2px dashed #dcdfe6;
      border-radius: 12px;
      background: #fafafa;
      cursor: pointer;
      transition: all 0.3s ease;
      color: #909399;
      padding: 0 20px;
      .el-icon {
        font-size: 24px;
        margin-bottom: 8px;
      }
      span {
        font-size: 13px;
      }
      &:hover {
        border-color: #409eff;
        background: #ecf5ff;
        color: #409eff;
      }
    }
  }
  .route-card-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }
  .route-card {
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    overflow: hidden;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 20px 40px;
      border-bottom: 1px solid #ebeef5;
      background: #f8f9fa;
      .route-info {
        display: flex;
        // flex-direction: column;
        // justify-content: center;
        // items-align: center;
        gap: 4px;
        .route-code {
          font-size: 12px;
          color: #909399;
          font-family: "Courier New", monospace;
          line-height: 30px;
        }
        .route-name {
          font-size: 18px;
          font-weight: 600;
          color: #303133;
          display: flex;
          align-items: center;
        }
      }
      .route-actions {
        display: flex;
        gap: 8px;
        // .el-button {
        //   color: #409eff;
        // }
      }
    }
    .card-body {
      padding: 16px 40px;
      .route-desc {
        font-size: 14px;
        color: #606266;
        margin-bottom: 12px;
      }
      .route-meta {
        display: flex;
        gap: 24px;
        margin-bottom: 12px;
        padding: 10px 14px;
        background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
        border-radius: 8px;
        border-left: 3px solid #409eff;
        .meta-item {
          display: flex;
          align-items: center;
          gap: 6px;
          font-size: 13px;
          margin-right: 40px;
          .el-icon {
            font-size: 14px;
            color: #409eff;
          }
          .meta-label {
            color: #909399;
            font-weight: 500;
          }
          .meta-value {
            color: #303133;
            font-weight: 600;
          }
        }
      }
      .expand-btn-wrapper {
        display: flex;
        justify-content: center;
        margin-top: 8px;
        .expand-btn {
          padding: 8px 20px;
          border-radius: 20px;
          background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
          border: 1px solid #b3d8ff;
          transition: all 0.3s ease;
          .btn-text {
            font-size: 13px;
            font-weight: 500;
            color: #409eff;
            margin-right: 6px;
          }
          .expand-icon {
            font-size: 14px;
            color: #409eff;
            transition: transform 0.3s ease;
          }
          &:hover {
            background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
            border-color: #409eff;
            box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
            .btn-text,
            .expand-icon {
              color: #fff;
            }
          }
          &.expanded {
            background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%);
            border-color: #a5d69a;
            .btn-text,
            .expand-icon {
              color: #67c23a;
            }
            &:hover {
              background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
              border-color: #67c23a;
              box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
              .btn-text,
              .expand-icon {
                color: #fff;
              }
            }
          }
        }
      }
    }
    .process-route {
      padding: 0 20px 20px;
      background: #f5f7fa;
      border-top: 1px solid #ebeef5;
      .process-flow {
        display: flex;
        align-items: flex-start;
        gap: 8px;
        padding: 20px 0;
        overflow-x: auto;
        overflow-y: hidden;
        .process-flow-item {
          display: flex;
          align-items: center;
          gap: 8px;
          .process-node {
            background: #fff;
            border-radius: 12px;
            padding: 16px;
            border: 2px solid #ebeef5;
            cursor: move;
            transition: all 0.3s ease;
            // min-width: 180px;
            // max-width: 220px;
            width: 300px;
            &.expanded {
              width: 400px;
            }
            &:hover {
              box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
              transform: translateY(-2px);
              border-color: #409eff;
            }
            &:active {
              cursor: grabbing;
            }
            .process-node-header {
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-bottom: 12px;
              .process-number {
                width: 28px;
                height: 28px;
                border-radius: 50%;
                background: #409eff;
                color: #ffffff;
                font-size: 12px;
                font-weight: 600;
                display: flex;
                align-items: center;
                justify-content: center;
              }
              .process-actions {
                display: flex;
                gap: 4px;
              }
            }
            .process-node-body {
              text-align: center;
              margin-bottom: 12px;
              .process-code {
                font-size: 11px;
                color: #909399;
                font-family: "Courier New", monospace;
                margin-bottom: 4px;
              }
              .process-name {
                font-size: 15px;
                font-weight: 600;
                color: #303133;
                margin-bottom: 6px;
              }
              .process-desc {
                font-size: 12px;
                color: #606266;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
              }
            }
            .process-node-footer {
              display: flex;
              justify-content: flex-end;
              align-items: center;
              padding-top: 10px;
              border-top: 1px solid #ebeef5;
            }
            .process-params-section {
              margin-top: 12px;
              padding-top: 12px;
              border-top: 1px solid #ebeef5;
              .params-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 8px;
                font-size: 13px;
                font-weight: 600;
                color: #303133;
              }
              .params-list {
                display: flex;
                flex-direction: column;
                gap: 6px;
                max-height: 200px;
                overflow-y: auto;
                .param-item {
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
                  padding: 6px 8px;
                  background: #fafafa;
                  border-radius: 4px;
                  border-left: 2px solid #409eff;
                  font-size: 12px;
                  .param-info {
                    display: flex;
                    flex-direction: row;
                    align-items: center;
                    gap: 6px;
                    flex: 1;
                    min-width: 0;
                    .param-code {
                      font-size: 11px;
                      color: #e6a23c;
                      font-family: "Courier New", monospace;
                      margin-right: 20px;
                    }
                    .param-name {
                      font-size: 12px;
                      color: #303133;
                      font-weight: 500;
                      margin-right: 20px;
                    }
                    .param-value {
                      font-size: 11px;
                      color: #606266;
                    }
                  }
                  .param-actions {
                    display: flex;
                    gap: 4px;
                    flex-shrink: 0;
                  }
                }
              }
            }
          }
          .flow-arrow {
            display: flex;
            align-items: center;
            color: #c0c4cc;
            font-size: 24px;
            padding: 0 4px;
            .el-icon {
              font-size: 20px;
            }
          }
        }
        .add-process-node {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          min-width: 100px;
          height: 175px;
          border: 2px dashed #dcdfe6;
          border-radius: 12px;
          background: #fafafa;
          cursor: pointer;
          transition: all 0.3s ease;
          color: #909399;
          // margin-left: 10px;
          .el-icon {
            font-size: 24px;
            margin-bottom: 8px;
          }
          span {
            font-size: 13px;
          }
          &:hover {
            border-color: #409eff;
            background: #ecf5ff;
            color: #409eff;
          }
        }
      }
    }
  }
  // æ‹–拽时的样式
  .process-flow-item.dragging {
    opacity: 0.5;
    transform: scale(0.98);
  }
  // é€‰æ‹©å·¥åºå¯¹è¯æ¡†æ ·å¼
  .process-select-container {
    display: flex;
    gap: 20px;
    height: 450px;
    .process-list-area {
      flex: 1;
      display: flex;
      flex-direction: column;
      .area-title {
        font-size: 14px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 12px;
        padding-bottom: 8px;
        border-bottom: 1px solid #ebeef5;
      }
      .search-box {
        margin-bottom: 12px;
        .el-input {
          width: 100%;
        }
      }
    }
    .process-detail-area {
      width: 380px;
      display: flex;
      flex-direction: column;
      background: #f5f7fa;
      border-radius: 8px;
      padding: 16px;
      .area-title {
        font-size: 14px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 16px;
        padding-bottom: 8px;
        border-bottom: 1px solid #ebeef5;
      }
      .process-detail-form {
        .el-form-item {
          margin-bottom: 12px;
          .el-form-item__label {
            color: #606266;
            font-weight: 500;
          }
        }
        .detail-text {
          color: #303133;
          font-weight: 500;
        }
      }
    }
  }
</style>
src/views/productionManagement/processRoute/processRouteItem/index.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/productStructure/Detail/index.vue
@@ -148,6 +148,7 @@
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           single
                           @confirm="handleProduct" />
  </div>
</template>
@@ -326,6 +327,7 @@
    // æ ¡éªŒå‡½æ•°
    const validateItem = (item: any, isTopLevel = false) => {
      console.log(item, "item");
      // æ ¡éªŒå½“前项的必填字段
      if (!item.model) {
        ElMessage.error("请选择规格");
src/views/productionManagement/productStructure/StructureEdit.vue
@@ -19,13 +19,10 @@
               @click="cancelEdit"
               style="margin-bottom: 10px">取消
    </el-button>
    <el-table
        :data="tableData"
        border
        :preserve-expanded-content="false"
        style="width: 100%"
    >
    <el-table :data="tableData"
              border
              :preserve-expanded-content="false"
              style="width: 100%">
      <el-table-column type="expand">
        <template #default="props">
          <el-form ref="form"
@@ -154,14 +151,18 @@
          </el-form>
        </template>
      </el-table-column>
      <el-table-column label="产品编码" prop="productCode" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" />
      <el-table-column label="单位" prop="unit" />
      <el-table-column label="产品编码"
                       prop="productCode" />
      <el-table-column label="产品名称"
                       prop="productName" />
      <el-table-column label="规格型号"
                       prop="model" />
      <el-table-column label="单位"
                       prop="unit" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           single
                           @confirm="handleProduct" />
    <template #footer>
      <div class="dialog-footer">
@@ -236,8 +237,8 @@
      model: props.record.model,
      unit: props.record.unit,
      productCode: props.record.productCode,
    }
  ]
    },
  ];
  const openDialog = index => {
    dataValue.currentRowIndex = index;
src/views/productionManagement/productStructure/index.vue
@@ -1,380 +1,483 @@
<template>
  <div class="app-container">
    <div style="text-align: right; margin-bottom: 10px;">
      <el-button type="info" plain icon="Upload" @click="handleImport"
        v-hasPermi="['product:bom:import']">导入</el-button>
      <el-button type="warning" plain icon="Download" @click="handleExport" :disabled="selectedRows.length !== 1"
        v-hasPermi="['product:bom:export']">导出</el-button>
      <el-button type="primary" @click="handleAdd">新增</el-button>
      <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button>
      <el-button type="info"
                 plain
                 icon="Upload"
                 @click="handleImport"
                 v-hasPermi="['product:bom:import']">导入</el-button>
      <el-button type="warning"
                 plain
                 icon="Download"
                 @click="handleExport"
                 :disabled="selectedRows.length !== 1"
                 v-hasPermi="['product:bom:export']">导出</el-button>
      <el-button type="primary"
                 @click="handleAdd">新增</el-button>
      <el-button type="danger"
                 plain
                 @click="handleBatchDelete"
                 :disabled="selectedRows.length === 0">删除</el-button>
    </div>
    <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true"
      @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination">
    <PIMTable rowKey="id"
              :column="tableColumn"
              :tableData="tableData"
              :page="page"
              :isSelection="true"
              @selection-change="handleSelectionChange"
              :tableLoading="tableLoading"
              @pagination="pagination">
      <template #detail="{ row }">
        <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }}
        <el-button type="primary"
                   text
                   @click="showDetail(row)">{{ row.bomNo }}
        </el-button>
      </template>
    </PIMTable>
    <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow" />
    <StructureEdit v-if="showEdit"
                   v-model:show-model="showEdit"
                   :record="currentRow" />
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? '新增BOM' : '编辑BOM'" width="600px"
      @close="closeDialog">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增BOM' : '编辑BOM'"
               width="600px"
               @close="closeDialog">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName || '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="版本号" prop="version">
          <el-input v-model="form.version" placeholder="请输入版本号" clearable />
        <el-form-item label="版本号"
                      prop="version">
          <el-input v-model="form.version"
                    placeholder="请输入版本号"
                    clearable />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" clearable />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="form.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入备注"
                    clearable />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
        <el-button type="primary"
                   @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¼¹çª— -->
    <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single />
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- BOM导入对话框 -->
    <ImportDialog ref="uploadRef" v-model="upload.open" :title="upload.title" :action="upload.url"
      :headers="upload.headers" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress"
      :on-success="handleFileSuccess" :show-download-template="true" @confirm="submitFileForm"
      @download-template="handleDownloadTemplate" @close="handleImportClose" />
    <ImportDialog ref="uploadRef"
                  v-model="upload.open"
                  :title="upload.title"
                  :action="upload.url"
                  :headers="upload.headers"
                  :disabled="upload.isUploading"
                  :on-progress="handleFileUploadProgress"
                  :on-success="handleFileSuccess"
                  :show-download-template="true"
                  @confirm="submitFileForm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
  </div>
</template>
<script setup>
import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
import { getToken } from "@/utils/auth";
import { listPage, add, update, batchDelete, exportBom, downloadTemplate } from "@/api/productionManagement/productBom.js";
import { useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import {
    ref,
    reactive,
    toRefs,
    onMounted,
    getCurrentInstance,
    defineAsyncComponent,
  } from "vue";
  import { getToken } from "@/utils/auth";
  import {
    listPage,
    add,
    update,
    batchDelete,
    exportBom,
    downloadTemplate,
  } from "@/api/productionManagement/productBom.js";
  import { useRouter } from "vue-router";
  import { ElMessageBox } from "element-plus";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
const router = useRouter()
const { proxy } = getCurrentInstance()
const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
  const router = useRouter();
  const { proxy } = getCurrentInstance();
  const StructureEdit = defineAsyncComponent(() =>
    import("@/views/productionManagement/productStructure/StructureEdit.vue")
  );
const tableColumn = ref([
  {
    label: "BOM编号",
    prop: "bomNo",
    dataType: 'slot',
    slot: "detail",
    minWidth: 140
  },
  {
    label: "产品名称",
    prop: "productName",
  const tableColumn = ref([
    {
      label: "BOM编号",
      prop: "bomNo",
      dataType: "slot",
      slot: "detail",
      minWidth: 140,
    },
    {
      label: "产品名称",
      prop: "productName",
    minWidth: 160
  },
  {
    label: "规格型号",
    prop: "productModelName",
    minWidth: 140
  },
  {
    label: "版本号",
    prop: "version",
    width: 100
  },
  {
    label: "备注",
    prop: "remark",
    minWidth: 160
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          handleEdit(row)
        }
      },
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          handleDelete(row)
        }
      }
    ]
  }
]);
      minWidth: 160,
    },
    {
      label: "规格型号",
      prop: "productModelName",
      minWidth: 140,
    },
    {
      label: "版本号",
      prop: "version",
      width: 100,
    },
    {
      label: "备注",
      prop: "remark",
      minWidth: 160,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 150,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          link: true,
          clickFun: row => {
            handleDelete(row);
          },
        },
      ],
    },
  ]);
const tableData = ref([]);
const tableLoading = ref(false);
const showEdit = ref(false);
const selectedRows = ref([]);
const currentRow = ref({});
const dialogVisible = ref(false);
const operationType = ref('add'); // add | edit
const formRef = ref(null);
const showProductSelectDialog = ref(false);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const showEdit = ref(false);
  const selectedRows = ref([]);
  const currentRow = ref({});
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const showProductSelectDialog = ref(false);
//  BOM导入参数
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(BOM导入)
  open: false,
  // å¼¹å‡ºå±‚标题(BOM导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom"
});
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  form: {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  },
  rules: {
    productModelId: [{ required: true, message: "请选择产品", trigger: "change" }],
    version: [{ required: true, message: "请输入版本号", trigger: "blur" }]
  }
});
const { form, rules } = toRefs(data);
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// åˆ†é¡µ
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
// æŸ¥è¯¢åˆ—表
const getList = () => {
  tableLoading.value = true;
  listPage({
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      const records = res?.data?.records || [];
      tableData.value = records;
      page.total = res?.data?.total || 0;
    })
    .catch((err) => {
      console.error("获取列表失败:", err);
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add';
  Object.assign(form.value, {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  //  BOM导入参数
  const upload = reactive({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(BOM导入)
    open: false,
    // å¼¹å‡ºå±‚标题(BOM导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom",
  });
  dialogVisible.value = true;
};
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  Object.assign(form.value, {
    id: row.id,
    productName: row.productName || "",
    productModelName: row.productModelName || "",
    productModelId: row.productModelId || "",
    remark: row.remark || "",
    version: row.version || ""
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  dialogVisible.value = true;
};
// åˆ é™¤ï¼ˆå•条)
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该BOM?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete([row.id])
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => { });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$modal.msgWarning('请选择数据');
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete(ids)
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => { });
};
// äº§å“é€‰æ‹©
const handleProductSelect = (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    form.value.productModelId = product.id;
    form.value.productName = product.productName;
    form.value.productModelName = product.model;
  }
  showProductSelectDialog.value = false;
};
// æäº¤è¡¨å•
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      const payload = { ...form.value };
      if (operationType.value === 'add') {
        add(payload)
          .then(() => {
            proxy.$modal.msgSuccess('新增成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('新增失败');
          });
      } else {
        update(payload)
          .then(() => {
            proxy.$modal.msgSuccess('修改成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('修改失败');
          });
      }
    }
  const data = reactive({
    form: {
      id: undefined,
      productName: "",
      productModelName: "",
      productModelId: "",
      remark: "",
      version: "",
    },
    rules: {
      productModelId: [
        { required: true, message: "请选择产品", trigger: "change" },
      ],
      version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
    },
  });
};
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogVisible.value = false;
  formRef.value?.resetFields();
};
  const { form, rules } = toRefs(data);
//  å¯¼å…¥æŒ‰é’®æ“ä½œ
const handleImport = () => {
  upload.title = "BOM导入";
  upload.open = true;
};
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
// å…³é—­å¯¼å…¥å¯¹è¯æ¡†æ—¶æ¸…除文件
const handleImportClose = () => {
  proxy.$refs["uploadRef"].clearFiles();
};
//  æ–‡ä»¶ä¸Šä¼ ä¸­å¤„理
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
//  æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç†
const handleFileSuccess = (response, file, fileList) => {
  upload.open = false;
  upload.isUploading = false;
  proxy.$refs["uploadRef"].clearFiles();
  if (response.code === 200) {
    proxy.$modal.msgSuccess(response.msg || "导入成功");
  // åˆ†é¡µ
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  } else {
    proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
  }
};
  };
// æäº¤ä¸Šä¼ æ–‡ä»¶
const submitFileForm = () => {
  proxy.$refs["uploadRef"].submit();
};
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    tableLoading.value = true;
    listPage({
      current: page.current,
      size: page.size,
    })
      .then(res => {
        const records = res?.data?.records || [];
        tableData.value = records;
        page.total = res?.data?.total || 0;
      })
      .catch(err => {
        console.error("获取列表失败:", err);
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
//  å¯¼å‡ºæŒ‰é’®æ“ä½œ
const handleExport = () => {
  if (selectedRows.value.length !== 1) {
    proxy.$modal.msgWarning("请选择一条数据进行导出");
    return;
  }
  // æ–°å¢ž
  const handleAdd = () => {
    operationType.value = "add";
    Object.assign(form.value, {
      id: undefined,
      productName: "",
      productModelName: "",
      productModelId: "",
      remark: "",
      version: "",
    });
    dialogVisible.value = true;
  };
  const bomId = selectedRows.value[0].id;
  const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(form.value, {
      id: row.id,
      productName: row.productName || "",
      productModelName: row.productModelName || "",
      productModelId: row.productModelId || "",
      remark: row.remark || "",
      version: row.version || "",
    });
    dialogVisible.value = true;
  };
  exportBom(bomId).then(res => {
    // è¿”回的数据是否为空
    if (!res) {
      proxy.$modal.msgError("导出失败,返回数据为空");
  // åˆ é™¤ï¼ˆå•条)
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该BOM?", "提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        batchDelete([row.id])
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // æ‰¹é‡åˆ é™¤
  const handleBatchDelete = () => {
    if (!selectedRows.value.length) {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        batchDelete(ids)
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // äº§å“é€‰æ‹©
  const handleProductSelect = products => {
    if (products && products.length > 0) {
      const product = products[0];
      form.value.productModelId = product.id;
      form.value.productName = product.productName;
      form.value.productModelName = product.model;
    }
    showProductSelectDialog.value = false;
  };
  // æäº¤è¡¨å•
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        const payload = { ...form.value };
        if (operationType.value === "add") {
          add(payload)
            .then(() => {
              proxy.$modal.msgSuccess("新增成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("新增失败");
            });
        } else {
          update(payload)
            .then(() => {
              proxy.$modal.msgSuccess("修改成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("修改失败");
            });
        }
      }
    });
  };
  // å…³é—­å¼¹çª—
  const closeDialog = () => {
    dialogVisible.value = false;
    formRef.value?.resetFields();
  };
  //  å¯¼å…¥æŒ‰é’®æ“ä½œ
  const handleImport = () => {
    upload.title = "BOM导入";
    upload.open = true;
  };
  // å…³é—­å¯¼å…¥å¯¹è¯æ¡†æ—¶æ¸…除文件
  const handleImportClose = () => {
    proxy.$refs["uploadRef"].clearFiles();
  };
  //  æ–‡ä»¶ä¸Šä¼ ä¸­å¤„理
  const handleFileUploadProgress = (event, file, fileList) => {
    upload.isUploading = true;
  };
  //  æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç†
  const handleFileSuccess = (response, file, fileList) => {
    upload.open = false;
    upload.isUploading = false;
    proxy.$refs["uploadRef"].clearFiles();
    if (response.code === 200) {
      proxy.$modal.msgSuccess(response.msg || "导入成功");
      getList();
    } else {
      proxy.$alert(
        "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
          response.msg +
          "</div>",
        "导入结果",
        { dangerouslyUseHTMLString: true }
      );
    }
  };
  // æäº¤ä¸Šä¼ æ–‡ä»¶
  const submitFileForm = () => {
    proxy.$refs["uploadRef"].submit();
  };
  //  å¯¼å‡ºæŒ‰é’®æ“ä½œ
  const handleExport = () => {
    if (selectedRows.value.length !== 1) {
      proxy.$modal.msgWarning("请选择一条数据进行导出");
      return;
    }
    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const downloadElement = document.createElement('a');
    const bomId = selectedRows.value[0].id;
    const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
    exportBom(bomId)
      .then(res => {
        // è¿”回的数据是否为空
        if (!res) {
          proxy.$modal.msgError("导出失败,返回数据为空");
          return;
        }
        const blob = new Blob([res], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        const downloadElement = document.createElement("a");
        const href = window.URL.createObjectURL(blob);
        downloadElement.style.display = "none";
        downloadElement.href = href;
        downloadElement.download = fileName;
        document.body.appendChild(downloadElement);
        downloadElement.click();
        document.body.removeChild(downloadElement);
        window.URL.revokeObjectURL(href);
        proxy.$modal.msgSuccess("导出成功");
      })
      .catch(err => {
        console.error("导出异常:", err);
        proxy.$modal.msgError("系统异常,导出失败");
      });
  };
  //  ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = async () => {
    const res = await downloadTemplate();
    // è¿”回的数据是否为空
    if (!res) {
      proxy.$modal.msgError("下载失败,返回数据为空");
      return;
    }
    const blob = new Blob([res], {
      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    });
    const downloadElement = document.createElement("a");
    const href = window.URL.createObjectURL(blob);
    downloadElement.style.display = 'none';
    downloadElement.href = href;
    downloadElement.download = fileName;
    downloadElement.download = "BOM模板.xlsx";
    document.body.appendChild(downloadElement);
    downloadElement.click();
@@ -382,52 +485,23 @@
    document.body.removeChild(downloadElement);
    window.URL.revokeObjectURL(href);
    proxy.$modal.msgSuccess("导出成功");
  }).catch(err => {
    console.error("导出异常:", err);
    proxy.$modal.msgError("系统异常,导出失败");
    proxy.$modal.msgSuccess("下载成功");
  };
  // æŸ¥çœ‹è¯¦æƒ…
  const showDetail = row => {
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productName || "",
        productModelName: row.productModelName || "",
      },
    });
  };
  onMounted(() => {
    getList();
  });
};
//  ä¸‹è½½æ¨¡æ¿
const handleDownloadTemplate = async () => {
  const res = await downloadTemplate();
  // è¿”回的数据是否为空
  if (!res) {
    proxy.$modal.msgError("下载失败,返回数据为空");
    return;
  }
  const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  const downloadElement = document.createElement('a');
  const href = window.URL.createObjectURL(blob);
  downloadElement.href = href;
  downloadElement.download = "BOM模板.xlsx";
  document.body.appendChild(downloadElement);
  downloadElement.click();
  document.body.removeChild(downloadElement);
  window.URL.revokeObjectURL(href);
  proxy.$modal.msgSuccess("下载成功");
};
// æŸ¥çœ‹è¯¦æƒ…
const showDetail = (row) => {
  router.push({
    path: '/productionManagement/productStructureDetail',
    query: {
      id: row.id,
      bomNo: row.bomNo || '',
      productName: row.productName || '',
      productModelName: row.productModelName || ''
    }
  });
};
onMounted(() => {
  getList();
});
</script>
src/views/productionManagement/productionOrder/New.vue
@@ -1,42 +1,37 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增生产订单"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
    <el-dialog v-model="isShow"
               title="新增生产订单"
               width="800"
               @close="closeModal">
      <el-form label-width="140px"
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品名称"
                      prop="productModelId"
                      :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            ]">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        <el-form-item label="规格"
                      prop="productModelName">
          <el-input v-model="formState.productModelName"
                    disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formState.unit"
                    disabled />
        </el-form-item>
        <el-form-item label="工艺路线">
          <el-select v-model="formState.routeId"
                     placeholder="请选择工艺路线"
@@ -48,24 +43,22 @@
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item
            label="需求数量"
            prop="quantity"
        >
          <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
        <el-form-item label="需求数量"
                      prop="quantity">
          <el-input-number v-model="formState.quantity"
                           :step="1"
                           :min="1"
                           style="width: 100%" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <ProductSelectDialog v-model="showProductSelectDialog"
                           @confirm="handleProductSelect"
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
@@ -74,119 +67,123 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js";
  import { ref, computed, getCurrentInstance } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import {
    addProductOrder,
    listProcessRoute,
  } from "@/api/productionManagement/productionOrder.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
    },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
});
    type: {
      type: String,
      required: true,
      default: "qualified",
    },
  });
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  routeId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  quantity: 0,
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
  // å“åº”式数据(替代选项式的 data)
  const formState = ref({
    productId: undefined,
    productModelId: undefined,
    routeId: undefined,
    productName: "",
    productModelName: "",
    quantity: '',
    unit: "",
    quantity: 0,
  });
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
  const showProductSelectDialog = ref(false);
  let { proxy } = getCurrentInstance();
  const closeModal = () => {
    // é‡ç½®è¡¨å•数据
    formState.value = {
      productId: undefined,
      productModelId: undefined,
      routeId: undefined,
      productName: "",
      productModelName: "",
      quantity: "",
    };
    isShow.value = false;
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    fetchRouteOptions( product.id);
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
const routeOptions = ref([]);
const bindRouteLoading = ref(false);
const fetchRouteOptions = (productModelId) => {
  formState.value.routeId = undefined;
  routeOptions.value = []
  bindRouteLoading.value = true;
  listProcessRoute({ productModelId: productModelId }).then(res => {
    routeOptions.value = res.data || [];
  }).finally(() => {
    bindRouteLoading.value = false;
  })
}
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      addProductOrder(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
  // äº§å“é€‰æ‹©å¤„理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      formState.value.productId = product.id;
      formState.value.productName = product.productName;
      formState.value.productModelName = product.model;
      formState.value.productModelId = product.id;
      formState.value.unit = product.unit;
      showProductSelectDialog.value = false;
      fetchRouteOptions(product.id);
      // è§¦å‘表单验证更新
      proxy.$refs["formRef"]?.validateField("productModelId");
    }
  })
};
  };
  const routeOptions = ref([]);
  const bindRouteLoading = ref(false);
  const fetchRouteOptions = productModelId => {
    formState.value.routeId = undefined;
    routeOptions.value = [];
    bindRouteLoading.value = true;
    listProcessRoute({ productModelId: productModelId })
      .then(res => {
        routeOptions.value = res.data || [];
      })
      .finally(() => {
        bindRouteLoading.value = false;
      });
  };
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
  const handleSubmit = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’Œè§„æ ¼
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择产品");
          return;
        }
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择规格");
          return;
        }
        addProductOrder(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit("completed");
          proxy.$modal.msgSuccess("提交成功");
        });
      }
    });
  };
  defineExpose({
    closeModal,
    handleSubmit,
    isShow,
  });
</script>
src/views/productionManagement/productionProcess/Edit.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/productionProcess/New.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/productionProcess/index.vue
@@ -1,314 +1,1084 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="工序名称:">
          <el-input v-model="searchForm.name"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工序编号:">
          <el-input v-model="searchForm.no"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item>
    <div class="process-config-container">
      <!-- å·¦ä¾§å·¥åºåˆ—表 -->
      <div class="process-list-section">
        <div class="section-header">
          <h3 class="section-title">工序列表</h3>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
                     size="small"
                     @click="handleAddProcess">
            <el-icon>
              <Plus />
            </el-icon>新增工序
          </el-button>
        </div>
        <div class="process-card-list"
             v-loading="processLoading">
          <div v-for="process in processValueList"
               :key="process.id"
               class="process-card"
               :class="{ active: selectedProcess?.id === process.id }"
               @click="selectProcess(process)">
            <div class="card-header">
              <div class="process-name">{{ process.name }} <span class="process-code">{{ process.no }}</span></div>
              <div class="card-actions">
                <el-button link
                           type="primary"
                           @click.stop="handleEditProcess(process)">
                  <el-icon>
                    <Edit />
                  </el-icon>
                  ç¼–辑
                </el-button>
                <el-button link
                           type="danger"
                           @click.stop="handleDeleteProcess(process)">
                  <el-icon>
                    <Delete />
                  </el-icon>
                  åˆ é™¤
                </el-button>
              </div>
            </div>
            <div class="card-body">
              <!-- <div class="process-name">{{ process.name }}</div> -->
              <div class="process-desc">{{ process.remark || '暂无描述' }}</div>
            </div>
            <div class="card-footer">
              <div class="status-tag"> <el-tag size="small"
                        :type="process.status ? 'success' : 'info'">
                  {{ process.status ? '启用' : '停用' }}
                </el-tag>
                <el-tag size="small"
                        :type="process.isQuality ? 'warning' : 'info'"
                        style="margin-left: 8px">
                  {{ process.isQuality ? '质检' : '非质检' }}
                </el-tag>
              </div>
              <span class="param-count">工资定额: Â¥{{ process.salaryQuota || 0 }}</span>
            </div>
          </div>
        </div>
      </div>
      <!-- å³ä¾§å‚数列表 -->
      <div class="param-list-section">
        <div class="section-header">
          <h3 class="section-title">
            {{ selectedProcess ? selectedProcess.name + ' - å‚数配置' : '请选择工序' }}
          </h3>
          <el-button type="primary"
                     size="small"
                     :disabled="!selectedProcess"
                     @click="handleSelectParam">
            <el-icon>
              <Plus />
            </el-icon>选择参数
          </el-button>
        </div>
        <div class="param-table-wrapper">
          <PIMTable v-if="selectedProcess"
                    rowKey="id"
                    :column="paramColumn"
                    :tableData="paramList"
                    :page="paramPage"
                    height="calc(100vh - 280px)"
                    :isSelection="false"
                    @pagination="handleParamPagination" />
          <div v-else
               class="empty-tip">
            <el-empty description="请从左侧选择一个工序" />
          </div>
        </div>
      </div>
    </div>
    <!-- å·¥åºæ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="processDialogVisible"
               :title="isProcessEdit ? '编辑工序' : '新增工序'"
               width="500px">
      <el-form :model="processForm"
               :rules="processRules"
               ref="processFormRef"
               label-width="100px">
        <el-form-item label="工序编码"
                      prop="no">
          <el-input v-model="processForm.no"
                    placeholder="请输入工序编码" />
        </el-form-item>
        <el-form-item label="工序名称"
                      prop="name">
          <el-input v-model="processForm.name"
                    placeholder="请输入工序名称" />
        </el-form-item>
        <el-form-item label="工资定额"
                      prop="salaryQuota">
          <el-input v-model="processForm.salaryQuota"
                    type="number"
                    :step="0.001" />
        </el-form-item>
        <el-form-item label="是否质检"
                      prop="isQuality">
          <el-switch v-model="processForm.isQuality"
                     :active-value="true"
                     inactive-value="false" />
        </el-form-item>
        <el-form-item label="工序描述"
                      prop="remark">
          <el-input v-model="processForm.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入工序描述" />
        </el-form-item>
        <el-form-item label="状态"
                      prop="status">
          <el-radio-group v-model="processForm.status">
            <el-radio :label="true">启用</el-radio>
            <el-radio :label="false">停用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div style="text-align: right"
           class="mb10">
        <el-button type="primary"
                   @click="showNewModal">新增工序</el-button>
        <el-button type="info"
                   plain
                   @click="handleImport">导入</el-button>
        <el-button type="danger"
                   @click="handleDelete"
                   :disabled="selectedRows.length === 0"
                   plain>删除工序</el-button>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="processDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleProcessSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- é€‰æ‹©å‚数对话框 -->
    <el-dialog v-model="paramDialogVisible"
               title="选择参数"
               width="1000px">
      <div class="param-select-container">
        <!-- å·¦ä¾§å‚数列表 -->
        <div class="param-list-area">
          <div class="area-title">可选参数</div>
          <div class="search-box">
            <el-input v-model="paramSearchKeyword"
                      placeholder="请输入参数名称搜索"
                      clearable
                      size="small"
                      @input="handleSelectParam">
              <template #prefix>
                <el-icon>
                  <Search />
                </el-icon>
              </template>
            </el-input>
          </div>
          <el-table :data="filteredParamList"
                    height="360"
                    border
                    highlight-current-row
                    @current-change="handleParamSelect">
            <el-table-column prop="paramName"
                             label="参数名称" />
            <el-table-column prop="paramType"
                             label="参数类型">
              <template #default="scope">
                <el-tag size="small"
                        :type="getParamTypeTag(scope.row.paramType)">
                  {{ getParamTypeText(scope.row.paramType) }}
                </el-tag>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <!-- å³ä¾§å‚数详情 -->
        <div class="param-detail-area">
          <div class="area-title">参数详情</div>
          <el-form v-if="selectedParam"
                   :model="selectedParam"
                   label-width="100px"
                   class="param-detail-form">
            <el-form-item label="参数名称">
              <span class="detail-text">{{ selectedParam.paramName }}</span>
            </el-form-item>
            <el-form-item label="参数模式">
              <el-tag size="small"
                      :type="selectedParam.valueMode == '1' ? 'success' : 'warning'">
                {{ selectedParam.valueMode == '1' ? '单值' : '区间' }}
              </el-tag>
            </el-form-item>
            <el-form-item label="参数类型">
              <el-tag size="small"
                      :type="getParamTypeTag(selectedParam.paramType)">
                {{ getParamTypeText(selectedParam.paramType) }}
              </el-tag>
            </el-form-item>
            <el-form-item label="参数格式">
              <span class="detail-text">{{ selectedParam.paramFormat || '-' }}</span>
            </el-form-item>
            <el-form-item label="单位">
              <span class="detail-text">{{ selectedParam.unit || '-' }}</span>
            </el-form-item>
            <el-form-item label="标准值"
                          v-if="selectedParam.valueMode == '1'">
              <el-input v-model="selectedParam.standardValue"
                        type="number"
                        placeholder="请输入默认值" />
            </el-form-item>
            <el-form-item label="最小值"
                          v-if="selectedParam.valueMode == '2'">
              <el-input v-model="selectedParam.minValue"
                        type="number"
                        placeholder="请输入最小值" />
            </el-form-item>
            <el-form-item label="最大值"
                          v-if="selectedParam.valueMode == '2'">
              <el-input v-model="selectedParam.maxValue"
                        type="number"
                        placeholder="请输入最大值" />
            </el-form-item>
            <el-form-item label="排序">
              <el-input v-model="selectedParam.sort"
                        type="number"
                        placeholder="请输入排序" />
            </el-form-item>
            <el-form-item label="是否必填">
              <el-switch v-model="selectedParam.isRequired"
                         :active-value="1"
                         :inactive-value="0" />
            </el-form-item>
          </el-form>
          <el-empty v-else
                    description="请从左侧选择参数" />
        </div>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <new-process v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 @completed="getList" />
    <edit-process v-if="isShowEditModal"
                  v-model:visible="isShowEditModal"
                  :record="record"
                  @completed="getList" />
    <ImportDialog ref="importDialogRef"
                  v-model="importDialogVisible"
                  title="导入工序"
                  :action="importAction"
                  :headers="importHeaders"
                  :auto-upload="false"
                  :on-success="handleImportSuccess"
                  :on-error="handleImportError"
                  @confirm="handleImportConfirm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="paramDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     :disabled="!selectedParam"
                     @click="handleParamSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç¼–辑参数对话框 -->
    <el-dialog v-model="editParamDialogVisible"
               title="编辑参数"
               width="600px">
      <el-form :model="editParamForm"
               :rules="editParamRules"
               ref="editParamFormRef"
               label-width="120px">
        <el-form-item label="参数名称">
          <span class="detail-text">{{ editParamForm.paramName }}</span>
        </el-form-item>
        <el-form-item label="参数模式">
          <el-tag size="small"
                  :type="editParamForm.valueMode == '1' ? 'success' : 'warning'">
            {{ editParamForm.valueMode == '1' ? '单值' : '区间' }}
          </el-tag>
        </el-form-item>
        <el-form-item label="标准值"
                      v-if="editParamForm.valueMode == '1'"
                      prop="standardValue">
          <el-input v-model="editParamForm.standardValue"
                    type="number"
                    placeholder="请输入标准值" />
        </el-form-item>
        <el-form-item label="最小值"
                      v-if="editParamForm.valueMode == '2'"
                      prop="minValue">
          <el-input v-model="editParamForm.minValue"
                    type="number"
                    placeholder="请输入最小值" />
        </el-form-item>
        <el-form-item label="最大值"
                      v-if="editParamForm.valueMode == '2'"
                      prop="maxValue">
          <el-input v-model="editParamForm.maxValue"
                    type="number"
                    placeholder="请输入最大值" />
        </el-form-item>
        <el-form-item label="排序"
                      prop="sort">
          <el-input v-model="editParamForm.sort"
                    type="number"
                    placeholder="请输入排序" />
        </el-form-item>
        <el-form-item label="是否必填"
                      prop="isRequired">
          <el-switch v-model="editParamForm.isRequired"
                     :active-value="1"
                     :inactive-value="0" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="editParamDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleEditParamSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import NewProcess from "@/views/productionManagement/productionProcess/New.vue";
  import EditProcess from "@/views/productionManagement/productionProcess/Edit.vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { ref, reactive, onMounted } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Edit, Delete, Search } from "@element-plus/icons-vue";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import { listType } from "@/api/system/dict/type";
  import {
    listPage,
    add,
    update,
    del,
    importData,
    downloadTemplate,
    list as getProcessListApi,
    processList,
    getProcessParamList,
    addProcessParam,
    editProcessParam,
    deleteProcessParam,
  } from "@/api/productionManagement/productionProcess.js";
  import { getToken } from "@/utils/auth";
  import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
  const data = reactive({
    searchForm: {
      name: "",
      no: "",
    },
  // å·¥åºåˆ—表数据
  const processValueList = ref([]);
  const selectedProcess = ref(null);
  const processLoading = ref(false);
  // å‚数列表数据
  const paramList = ref([]);
  const paramLoading = ref(false);
  const paramPage = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
  // æ•°æ®å­—å…¸
  const dictTypes = ref([]);
  // å·¥åºå¯¹è¯æ¡†
  const processDialogVisible = ref(false);
  const isProcessEdit = ref(false);
  const processFormRef = ref(null);
  const processForm = reactive({
    id: null,
    no: "",
    name: "",
    salaryQuota: null,
    isQuality: false,
    remark: "",
    status: true,
  });
  const processRules = {
    no: [{ required: true, message: "请输入工序编码", trigger: "blur" }],
    name: [{ required: true, message: "请输入工序名称", trigger: "blur" }],
    salaryQuota: [
      {
        required: true,
        message: "请输入工资定额",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value === null || value === undefined || value === "") {
            callback(new Error("请输入工资定额"));
          } else if (isNaN(value) || value < 0) {
            callback(new Error("工资定额必须是非负数字"));
          } else {
            callback();
          }
        },
      },
    ],
  };
  // å‚数对话框
  const paramDialogVisible = ref(false);
  const availableParamList = ref([]);
  const filteredParamList = ref([]);
  const selectedParam = ref(null);
  const paramSearchKeyword = ref("");
  // ç¼–辑参数对话框
  const editParamDialogVisible = ref(false);
  const editParamFormRef = ref(null);
  const editParamForm = reactive({
    id: null,
    processId: null,
    paramId: null,
    paramName: "",
    valueMode: "1",
    standardValue: null,
    minValue: null,
    maxValue: null,
    sort: 1,
    isRequired: 0,
    tenantId: 1,
  });
  const editParamRules = {
    standardValue: [
      {
        required: true,
        message: "请输入标准值",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value === null || value === undefined || value === "") {
            callback(new Error("请输入标准值"));
          } else {
            callback();
          }
        },
      },
    ],
    minValue: [
      {
        required: true,
        message: "请输入最小值",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value === null || value === undefined || value === "") {
            callback(new Error("请输入最小值"));
          } else {
            callback();
          }
        },
      },
    ],
    maxValue: [
      {
        required: true,
        message: "请输入最大值",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value === null || value === undefined || value === "") {
            callback(new Error("请输入最大值"));
          } else {
            callback();
          }
        },
      },
    ],
    sort: [
      {
        required: true,
        message: "请输入排序",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value === null || value === undefined || value === "") {
            callback(new Error("请输入排序"));
          } else if (isNaN(value) || value < 1) {
            callback(new Error("排序必须是大于0的整数"));
          } else {
            callback();
          }
        },
      },
    ],
  };
  // å‚数表格列配置
  const paramColumn = ref([
    {
      label: "工序编号",
      prop: "no",
      label: "参数名称",
      prop: "paramName",
    },
    {
      label: "工序名称",
      prop: "name",
      label: "参数模式",
      prop: "valueMode",
      dataType: "tag",
      formatType: row => (row.valueMode === "1" ? "success" : "warning"),
      formatData: row => (row.valueMode === "1" ? "单值" : "区间"),
    },
    {
      label: "工资定额",
      prop: "salaryQuota",
    },
    {
      label: "是否质检",
      prop: "isQuality",
      formatData: (params) => {
        return params ? "是" : "否";
      label: "参数类型",
      prop: "paramType",
      dataType: "tag",
      formatType: params => {
        const typeMap = {
          1: "primary",
          2: "info",
          3: "warning",
          4: "success",
        };
        return typeMap[params] || "default";
      },
      formatData: val => {
        const labelMap = {
          1: "数值格式",
          2: "文本格式",
          3: "下拉选项",
          4: "时间格式",
        };
        return labelMap[val] || val;
      },
    },
    {
      label: "备注",
      prop: "remark",
      label: "取值格式",
      prop: "paramFormat",
      formatData: (val, row) => {
        if (row.paramType == "3") {
          const dict = dictTypes.value.find(item => item.dictType === val);
          return dict ? "字典:" + dict.dictName : val;
        }
        return val;
      },
    },
    {
      label: "更新时间",
      prop: "updateTime",
      label: "标准值",
      prop: "standardValue",
      formatData: (val, row) => {
        return row.valueMode == "1" ? val : "-";
      },
    },
    {
      dataType: "action",
      label: "最小值",
      prop: "minValue",
      formatData: (val, row) => {
        return row.valueMode == "2" ? val : "-";
      },
    },
    {
      label: "最大值",
      prop: "maxValue",
      formatData: (val, row) => {
        return row.valueMode == "2" ? val : "-";
      },
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "排序",
      prop: "sort",
    },
    {
      label: "是否必填",
      prop: "isRequired",
      dataType: "tag",
      formatType: row => (row.isRequired === 1 ? "success" : "info"),
      formatData: row => (row.isRequired === 1 ? "是" : "否"),
    },
    {
      label: "操作",
      align: "center",
      fixed: "right",
      width: 280,
      dataType: "action",
      width: "150",
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            showEditModal(row);
          },
          clickFun: row => handleEditParam(row),
        },
        {
          name: "删除",
          clickFun: row => handleDeleteParam(row),
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const isShowNewModal = ref(false);
  const isShowEditModal = ref(false);
  const record = ref({});
  const importDialogVisible = ref(false);
  const importDialogRef = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const { proxy } = getCurrentInstance();
  // å¯¼å…¥ç›¸å…³é…ç½®
  const importAction =
    import.meta.env.VITE_APP_BASE_API + "/productProcess/importData";
  const importHeaders = { Authorization: "Bearer " + getToken() };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    listPage(params)
  // èŽ·å–å·¥åºåˆ—è¡¨
  const getProcessList = () => {
    processLoading.value = true;
    getProcessListApi()
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
        }));
        page.total = res.data.total;
        processValueList.value = res.data || [];
      })
      .catch(err => {
        tableLoading.value = false;
      .catch(() => {
        ElMessage.error("获取工序列表失败");
      })
      .finally(() => {
        processLoading.value = false;
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  // èŽ·å–å‚æ•°åˆ—è¡¨
  const getParamList = processId => {
    paramLoading.value = true;
    getProcessParamList(processId)
      .then(res => {
        paramList.value = res.data || [];
        paramPage.total = paramList.value.length;
      })
      .catch(() => {
        ElMessage.error("获取参数列表失败");
      })
      .finally(() => {
        paramLoading.value = false;
      });
  };
  // æ‰“开新增弹框
  const showNewModal = () => {
    isShowNewModal.value = true;
  // é€‰æ‹©å·¥åº
  const selectProcess = process => {
    selectedProcess.value = process;
    getParamList(process.id);
  };
  const showEditModal = row => {
    isShowEditModal.value = true;
    record.value = row;
  // å·¥åºæ“ä½œ
  const handleAddProcess = () => {
    isProcessEdit.value = false;
    processForm.id = null;
    processForm.no = "";
    processForm.name = "";
    processForm.salaryQuota = null;
    processForm.isQuality = false;
    processForm.remark = "";
    processForm.status = true;
    processDialogVisible.value = true;
  };
  // åˆ é™¤
  function handleDelete() {
    const no = selectedRows.value.map(item => item.no);
    const ids = selectedRows.value.map(item => item.id);
    if (no.length > 2) {
      proxy.$modal
        .confirm(
          '是否确认删除工序编号为"' +
            no[0] +
            "、" +
            no[1] +
            '"等' +
            no.length +
            "条数据项?"
        )
        .then(function () {
          return del(ids);
        })
  const handleEditProcess = process => {
    isProcessEdit.value = true;
    processForm.id = process.id;
    processForm.no = process.no;
    processForm.name = process.name;
    processForm.salaryQuota = process.salaryQuota;
    processForm.isQuality = process.isQuality || false;
    processForm.remark = process.remark || "";
    processForm.status = process.status;
    processDialogVisible.value = true;
  };
  const handleDeleteProcess = process => {
    ElMessageBox.confirm("确定要删除该工序吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      del([process.id])
        .then(() => {
          getList();
          proxy.$modal.msgSuccess("删除成功");
          ElMessage.success("删除成功");
          getProcessList();
          if (selectedProcess.value?.id === process.id) {
            selectedProcess.value = null;
            paramList.value = [];
          }
        })
        .catch(() => {});
    } else {
      proxy.$modal
        .confirm('是否确认删除工序编号为"' + no + '"的数据项?')
        .then(function () {
          return del(ids);
        })
        .then(() => {
          getList();
          proxy.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    }
  }
  // å¯¼å…¥
  const handleImport = () => {
    importDialogVisible.value = true;
        .catch(() => {
          ElMessage.error("删除失败");
        });
    });
  };
  // ç¡®è®¤å¯¼å…¥
  const handleImportConfirm = () => {
    if (importDialogRef.value) {
      importDialogRef.value.submit();
    }
  };
  // å¯¼å…¥æˆåŠŸ
  const handleImportSuccess = response => {
    if (response.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importDialogVisible.value = false;
      if (importDialogRef.value) {
        importDialogRef.value.clearFiles();
  const handleProcessSubmit = () => {
    processFormRef.value.validate(valid => {
      if (valid) {
        const apiMethod = isProcessEdit.value ? update : add;
        apiMethod(processForm)
          .then(() => {
            ElMessage.success(isProcessEdit.value ? "编辑成功" : "新增成功");
            processDialogVisible.value = false;
            getProcessList();
          })
          .catch(() => {
            ElMessage.error(isProcessEdit.value ? "编辑失败" : "新增失败");
          });
      }
      getList();
    });
  };
  // å‚数操作
  const handleSelectParam = () => {
    if (!selectedProcess.value) {
      ElMessage.warning("请先选择一个工序");
      return;
    }
    // èŽ·å–å¯é€‰å‚æ•°åˆ—è¡¨
    getBaseParamList({ paramName: paramSearchKeyword.value }).then(res => {
      if (res.code === 200) {
        filteredParamList.value = res.data || [];
        page.total = res.data?.length || 0;
      } else {
        ElMessage.error(res.msg || "查询失败");
      }
    });
    console.log(filteredParamList.value, "可选参数列表");
    selectedParam.value = null;
    paramDialogVisible.value = true;
  };
  const handleParamSelect = row => {
    selectedParam.value = row;
  };
  const handleParamSearch = () => {
    const keyword = paramSearchKeyword.value.trim().toLowerCase();
    if (!keyword) {
      filteredParamList.value = availableParamList.value;
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
      filteredParamList.value = availableParamList.value.filter(item =>
        item.paramName.toLowerCase().includes(keyword)
      );
    }
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = error => {
    proxy.$modal.msgError("导入失败:" + (error.message || "未知错误"));
  const getParamTypeText = type => {
    const typeMap = {
      1: "数值格式",
      2: "文本格式",
      3: "下拉选项",
      4: "时间格式",
    };
    return typeMap[type] || "未知参数类型";
  };
  const getParamTypeTag = type => {
    const typeMap = {
      1: "primary",
      2: "info",
      3: "warning",
      4: "success",
    };
    return typeMap[type] || "default";
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    if (importDialogRef.value) {
      importDialogRef.value.clearFiles();
  const handleDeleteParam = row => {
    ElMessageBox.confirm("确定要删除该参数吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      deleteProcessParam(row.id)
        .then(() => {
          ElMessage.success("删除成功");
          getParamList(selectedProcess.value.id);
        })
        .catch(() => {
          ElMessage.error("删除失败");
        });
    });
  };
  const handleEditParam = row => {
    editParamForm.id = row.id;
    editParamForm.processId = row.processId;
    editParamForm.paramId = row.paramId;
    editParamForm.paramName = row.paramName;
    editParamForm.valueMode = row.valueMode;
    editParamForm.standardValue = row.standardValue;
    editParamForm.minValue = row.minValue;
    editParamForm.maxValue = row.maxValue;
    editParamForm.sort = row.sort || 1;
    editParamForm.isRequired = row.isRequired || 0;
    editParamForm.tenantId = 1;
    editParamDialogVisible.value = true;
  };
  const handleEditParamSubmit = () => {
    editParamFormRef.value.validate(valid => {
      if (valid) {
        editProcessParam(editParamForm)
          .then(() => {
            ElMessage.success("编辑成功");
            editParamDialogVisible.value = false;
            getParamList(selectedProcess.value.id);
          })
          .catch(() => {
            ElMessage.error("编辑失败");
          });
      }
    });
  };
  const handleParamSubmit = () => {
    if (!selectedParam.value) {
      ElMessage.warning("请先选择一个参数");
      return;
    }
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = async () => {
    try {
      const res = await downloadTemplate();
      const blob = new Blob([res], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    addProcessParam({
      processId: selectedProcess.value.id,
      paramId: selectedParam.value.id,
      sort: selectedParam.value.sort || 1,
      standardValue: selectedParam.value.standardValue,
      minValue: selectedParam.value.minValue,
      maxValue: selectedParam.value.maxValue,
      isRequired: selectedParam.value.isRequired || 0,
      tenantId: 1,
    })
      .then(() => {
        ElMessage.success("添加成功");
        paramDialogVisible.value = false;
        getParamList(selectedProcess.value.id);
      })
      .catch(() => {
        ElMessage.error("添加失败");
      });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = "工序导入模板.xlsx";
      link.click();
      window.URL.revokeObjectURL(url);
      proxy.$modal.msgSuccess("模板下载成功");
    } catch (error) {
      proxy.$modal.msgError("模板下载失败");
    }
  };
  // å¯¼å‡º
  // const handleOut = () => {
  //     ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
  //         confirmButtonText: "确认",
  //         cancelButtonText: "取消",
  //         type: "warning",
  //     })
  //         .then(() => {
  //             proxy.download("/salesLedger/scheduling/exportTwo", {}, "工序排产.xlsx");
  //         })
  //         .catch(() => {
  //             proxy.$modal.msg("已取消");
  //         });
  // };
  const handleParamPagination = obj => {
    paramPage.current = obj.page;
    paramPage.size = obj.limit;
    getParamList(selectedProcess.value.id);
  };
  // èŽ·å–æ•°æ®å­—å…¸
  const getDictTypes = () => {
    listType({ pageNum: 1, pageSize: 1000 }).then(res => {
      dictTypes.value = res.rows || [];
    });
  };
  onMounted(() => {
    getList();
    getProcessList();
    getDictTypes();
  });
</script>
<style scoped></style>
<style scoped lang="scss">
  .app-container {
    padding: 20px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 84px);
  }
  .process-config-container {
    display: flex;
    gap: 20px;
    height: calc(100vh - 124px);
  }
  // å·¦ä¾§å·¥åºåˆ—表
  .process-list-section {
    width: 370px;
    min-width: 370px;
    flex-shrink: 0;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
  }
  .section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px 20px;
    border-bottom: 1px solid #ebeef5;
    .section-title {
      margin: 0;
      font-size: 16px;
      font-weight: 600;
      color: #303133;
    }
  }
  .process-card-list {
    flex: 1;
    overflow-y: auto;
    padding: 16px;
  }
  .process-card {
    background: #fff;
    border: 1px solid #ebeef5;
    border-radius: 8px;
    padding: 16px;
    margin-bottom: 12px;
    cursor: pointer;
    transition: all 0.3s ease;
    &:hover {
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      transform: translateY(-2px);
    }
    &.active {
      border-color: #409eff;
      background: #f5f7fa;
      box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
    }
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 8px;
      .process-code {
        font-size: 12px;
        // color: #909399;
        color: #cb9b18;
        font-family: "Courier New", monospace;
      }
      .card-actions {
        display: flex;
        gap: 4px;
        .el-button {
          padding: 4px;
        }
      }
    }
    .card-body {
      margin-bottom: 12px;
      .process-name {
        font-size: 16px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 4px;
      }
      .process-desc {
        font-size: 12px;
        color: #909399;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
    .card-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      .param-count {
        font-size: 12px;
        color: #606266;
      }
    }
  }
  // å³ä¾§å‚数列表
  .param-list-section {
    flex: 1;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
  }
  .param-table-wrapper {
    flex: 1;
    padding: 0 20px 20px;
    overflow: auto;
  }
  /* è¡¨æ ¼æ¨ªå‘滚动 */
  :deep(.el-table) {
    // min-width: 800px;
  }
  .empty-tip {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  // è¡¨æ ¼æ ·å¼
  :deep(.el-table) {
    border: none;
    border-radius: 6px;
    overflow: hidden;
    .el-table__header-wrapper {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      th {
        background: transparent;
        font-weight: 600;
        color: #ffffff;
        border-bottom: none;
        padding: 16px 0;
      }
    }
    .el-table__body-wrapper {
      tr {
        transition: all 0.3s ease;
        &:hover {
          background: linear-gradient(
            90deg,
            rgba(102, 126, 234, 0.05) 0%,
            rgba(118, 75, 162, 0.05) 100%
          );
        }
        td {
          border-bottom: 1px solid #f0f0f0;
          padding: 14px 0;
          color: #303133;
        }
      }
    }
  }
  // ç¼–码单元格样式
  :deep(.code-cell) {
    color: #e6a23c;
    font-family: "Courier New", monospace;
    font-weight: 500;
  }
  // æ•°å€¼å•元格样式
  :deep(.quantity-cell) {
    font-weight: 600;
    color: #409eff;
    font-family: "Courier New", monospace;
  }
  // é€‰æ‹©å‚数对话框样式
  .param-select-container {
    display: flex;
    gap: 20px;
    height: 450px;
    .param-list-area {
      // flex: 1;
      width: 380px;
      display: flex;
      flex-direction: column;
      .area-title {
        font-size: 14px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 12px;
        padding-bottom: 8px;
        border-bottom: 1px solid #ebeef5;
      }
      .search-box {
        margin-bottom: 12px;
        .el-input {
          width: 100%;
        }
      }
    }
    .param-detail-area {
      // width: 380px;
      flex: 1;
      display: flex;
      flex-direction: column;
      background: #f5f7fa;
      border-radius: 8px;
      padding: 16px;
      .area-title {
        font-size: 14px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 16px;
        padding-bottom: 8px;
        border-bottom: 1px solid #ebeef5;
      }
      .param-detail-form {
        .el-form-item {
          margin-bottom: 12px;
          .el-form-item__label {
            color: #606266;
            font-weight: 500;
          }
        }
        .detail-text {
          color: #303133;
          font-weight: 500;
        }
      }
    }
  }
</style>
src/views/productionPlan/productionPlan/index.vue
@@ -18,7 +18,7 @@
                    @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="产品规格:">
          <el-input v-model="searchForm.specification"
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    style="width: 160px;"
@@ -102,7 +102,7 @@
        <el-row :gutter="20">
          <el-col :span="10">
            <el-form-item label="产品规格">
              <div class="info-display">{{ mergeForm.specification || '-' }}</div>
              <div class="info-display">{{ mergeForm.model || '-' }}</div>
            </el-form-item>
          </el-col>
          <el-col :span="10">
@@ -251,9 +251,9 @@
                     filterable
                     placeholder="请选择">
            <el-option v-for="item in specificationOptions"
                       :key="item.skuId"
                       :label="item.specification"
                       :value="item.skuId" />
                       :key="item.id"
                       :label="item.model"
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="块数"
@@ -398,7 +398,7 @@
    },
    {
      label: "产品规格",
      prop: "specification",
      prop: "model",
      width: "150px",
      className: "spec-cell",
    },
@@ -549,7 +549,7 @@
            mergeForm.ids = [row.id];
            mergeForm.materialCode = row.materialCode;
            mergeForm.productName = row.productName || "";
            mergeForm.specification = row.specification || "";
            mergeForm.model = row.model || "";
            mergeForm.length = row.length || 0;
            mergeForm.width = row.width || 0;
            mergeForm.height = row.height || 0;
@@ -591,7 +591,7 @@
  const mergeForm = reactive({
    materialCode: "",
    productName: "",
    specification: "",
    model: "",
    length: 0,
    width: 0,
    height: 0,
@@ -632,7 +632,7 @@
    productMaterialId: undefined,
    productMaterialSkuId: undefined,
    productName: "",
    specification: "",
    model: "",
    materialCode: "",
    quantity: 0,
    volume: 0,
@@ -699,7 +699,7 @@
      if (item.materialList && item.materialList.length > 0) {
        newItem.children = item.materialList.map(material => ({
          value: material.id, // ä½¿ç”¨material的id作为value
          label: material.materialName, // ä½¿ç”¨materialName作为label
          label: material.productName, // ä½¿ç”¨materialName作为label
        }));
      }
@@ -712,28 +712,26 @@
    fetchSpecificationOptions(value);
  };
  const fetchSpecificationOptions = materialId => {
  const fetchSpecificationOptions = productId => {
    specificationOptions.value = [];
    if (materialId) {
      modelListPage({ materialId: materialId, size: -1, current: -1 }).then(
        res => {
          specificationOptions.value = res.data.records;
        }
      );
    if (productId) {
      modelListPage({ productId: productId, size: -1, current: -1 }).then(res => {
        specificationOptions.value = res.data.records;
      });
    }
  };
  const handleChangeSpecification = value => {
    form.materialCode = undefined;
    const selectedModel = specificationOptions.value.find(
      item => item.skuId === value
      item => item.id === value
    );
    if (selectedModel) {
      form.materialCode = selectedModel.materialCode;
      // è§£æžè§„格字符串获取长宽高
      const specification = selectedModel.specification;
      if (specification) {
        const dimensions = specification.match(/^(\d+)\*(\d+)\*(\d+)$/);
      const model = selectedModel.model;
      if (model) {
        const dimensions = model.match(/^(\d+)\*(\d+)\*(\d+)$/);
        if (dimensions && dimensions.length === 4) {
          form.length = parseInt(dimensions[1]);
          form.width = parseInt(dimensions[2]);
@@ -818,7 +816,7 @@
    searchForm: {
      customerName: "",
      productName: "",
      specification: "",
      model: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
@@ -838,7 +836,7 @@
    Object.assign(searchForm.value, {
      customerName: "",
      productName: "",
      specification: "",
      model: "",
      materialCode: "",
      applyNo: "",
      dateRange: [],
@@ -955,7 +953,7 @@
    const firstRow = selectedRows.value[0];
    mergeForm.materialCode = selectedserialNo.value;
    mergeForm.productName = firstRow.productName || "";
    mergeForm.specification = firstRow.specification || "";
    mergeForm.model = firstRow.model || "";
    mergeForm.length = firstRow.length || 0;
    mergeForm.width = firstRow.width || 0;
    mergeForm.height = firstRow.height || 0;
@@ -1091,7 +1089,7 @@
      productName: "",
      productMaterialId: undefined,
      productMaterialSkuId: undefined,
      specification: "",
      model: "",
      materialCode: "",
      quantity: 0,
      volume: 0,
@@ -1118,7 +1116,7 @@
      productName: row.productName || "",
      productMaterialId: row.productMaterialId || undefined,
      productMaterialSkuId: row.productMaterialSkuId || undefined,
      specification: row.specification || "",
      model: row.model || "",
      materialCode: row.materialCode || "",
      quantity: row.quantity || 0,
      volume: row.volume || 0,
src/views/productionPlan/summaryByProduct/index.vue
@@ -54,7 +54,7 @@
    },
    {
      label: "产品规格",
      prop: "specification",
      prop: "model",
      className: "spec-cell",
    },
    {
src/views/qualityManagement/metricBinding/index.vue
@@ -1,540 +1,578 @@
<template>
  <div class="app-container metric-binding">
    <el-row :gutter="16" class="metric-binding-row">
    <el-row :gutter="16"
            class="metric-binding-row">
      <!-- å·¦ä¾§ï¼šæ£€æµ‹æ ‡å‡†åˆ—表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="14" :xl="14" class="left-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="14"
              :xl="14"
              class="left-col">
        <div class="panel left-panel">
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="false"
        :rowClassName="rowClassNameCenter"
        :tableLoading="tableLoading"
        :rowClick="handleTableRowClick"
        @pagination="handlePagination"
        :total="page.total"
      >
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
        </template>
        <!-- è¡¨å¤´æœç´¢ -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #inspectTypeHeader>
          <el-select
            v-model="searchForm.inspectType"
            placeholder="类别"
            clearable
            size="small"
            style="width: 120px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="原材料检验" value="0" />
            <el-option label="过程检验" value="1" />
            <el-option label="出厂检验" value="2" />
          </el-select>
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
          <PIMTable rowKey="id"
                    :column="standardColumns"
                    :tableData="standardTableData"
                    :page="page"
                    :isSelection="false"
                    :rowClassName="rowClassNameCenter"
                    :tableLoading="tableLoading"
                    :rowClick="handleTableRowClick"
                    @pagination="handlePagination"
                    :total="page.total">
            <template #standardNoCell="{ row }">
              <span class="clickable-link"
                    @click="handleStandardRowClick(row)">
                {{ row.standardNo }}
              </span>
            </template>
            <!-- è¡¨å¤´æœç´¢ -->
            <template #standardNoHeader>
              <el-input v-model="searchForm.standardNo"
                        placeholder="标准编号"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #standardNameHeader>
              <el-input v-model="searchForm.standardName"
                        placeholder="标准名称"
                        clearable
                        size="small"
                        @change="handleQuery"
                        @clear="handleQuery" />
            </template>
            <template #inspectTypeHeader>
              <el-select v-model="searchForm.inspectType"
                         placeholder="类别"
                         clearable
                         size="small"
                         style="width: 120px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="原材料检验"
                           value="0" />
                <el-option label="过程检验"
                           value="1" />
                <el-option label="出厂检验"
                           value="2" />
              </el-select>
            </template>
            <template #stateHeader>
              <el-select v-model="searchForm.state"
                         placeholder="状态"
                         clearable
                         size="small"
                         style="width: 110px"
                         @change="handleQuery"
                         @clear="handleQuery">
                <el-option label="草稿"
                           value="0" />
                <el-option label="通过"
                           value="1" />
                <el-option label="撤销"
                           value="2" />
              </el-select>
            </template>
          </PIMTable>
        </div>
      </el-col>
      <!-- å³ä¾§ï¼šç»‘定列表 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="10" class="right-col">
      <el-col :xs="24"
              :sm="24"
              :md="12"
              :lg="10"
              :xl="10"
              class="right-col">
        <div class="panel right-panel">
      <div class="right-header">
        <div class="title">绑定关系</div>
        <div class="desc" v-if="currentStandard">
          å½“前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请选择左侧检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">添加绑定</el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">删除</el-button>
      </div>
      <el-table
        v-loading="bindingLoading"
        :data="bindingTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleBindingSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="productName" label="产品名称" min-width="140" />
        <el-table-column label="操作" width="120" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
          <div class="right-header">
            <div class="title">绑定关系</div>
            <div class="desc"
                 v-if="currentStandard">
              å½“前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
            </div>
            <div class="desc"
                 v-else>请选择左侧检测标准</div>
          </div>
          <div class="right-toolbar">
            <el-button type="primary"
                       :disabled="!currentStandard"
                       @click="openBindingDialog">添加绑定</el-button>
            <el-button type="danger"
                       plain
                       :disabled="!currentStandard"
                       @click="handleBatchUnbind">删除</el-button>
          </div>
          <el-table v-loading="bindingLoading"
                    :data="bindingTableData"
                    border
                    :row-class-name="() => 'row-center'"
                    class="center-table"
                    style="width: 100%"
                    height="calc(100vh - 220px)"
                    @selection-change="handleBindingSelectionChange">
            <el-table-column type="selection"
                             width="48"
                             align="center" />
            <el-table-column type="index"
                             label="序号"
                             width="60"
                             align="center" />
            <el-table-column prop="productName"
                             label="产品名称"
                             min-width="140" />
            <el-table-column label="操作"
                             width="120"
                             fixed="right"
                             align="center">
              <template #default="{ row }">
                <el-button link
                           type="danger"
                           size="small"
                           @click="handleUnbind(row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-col>
    </el-row>
    <!-- æ·»åŠ ç»‘å®šå¼¹æ¡† -->
    <el-dialog
      v-model="bindingDialogVisible"
      title="添加绑定"
      width="520px"
      @close="closeBindingDialog"
    >
    <el-dialog v-model="bindingDialogVisible"
               title="添加绑定"
               width="520px"
               @close="closeBindingDialog">
      <el-form label-width="100px">
        <el-form-item label="产品">
          <el-tree-select
            v-model="selectedProductIds"
            multiple
            collapse-tags
            collapse-tags-tooltip
            placeholder="请选择产品(可多选)"
            clearable
            check-strictly
            :data="productOptions"
            :render-after-expand="false"
            style="width: 100%"
          />
          <el-button type="primary"
                     @click="openProductSelectDialog">选择产品</el-button>
          <div class="selected-products mt-2"
               v-if="selectedProducts.length > 0">
            <el-tag v-for="product in selectedProducts"
                    :key="product.id"
                    closable
                    @close="removeSelectedProduct(product.id)"
                    class="mr-2 mb-2">
              {{ product.productName }} - {{ product.model }}
            </el-tag>
          </div>
          <div v-else
               class="text-gray-400"></div>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeBindingDialog">取消</el-button>
          <el-button type="primary" @click="submitBinding">确定</el-button>
          <el-button type="primary"
                     :disabled="selectedProducts.length === 0"
                     @click="submitBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <ProductSelectDialog v-model="productSelectDialogVisible"
                         :single="false"
                         @confirm="handleProductSelect" />
  </div>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from 'element-plus'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { productTreeList } from '@/api/basicData/product.js'
import {
  qualityTestStandardListPage
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import {
  qualityTestStandardBindingList,
  qualityTestStandardBindingAdd,
  qualityTestStandardBindingDel
} from '@/api/qualityManagement/qualityTestStandardBinding.js'
  import { Search } from "@element-plus/icons-vue";
  import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
  import { ElMessageBox, ElMessage } from "element-plus";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { qualityTestStandardListPage } from "@/api/qualityManagement/metricMaintenance.js";
  import { productProcessListPage } from "@/api/basicData/productProcess.js";
  import {
    qualityTestStandardBindingList,
    qualityTestStandardBindingAdd,
    qualityTestStandardBindingDel,
  } from "@/api/qualityManagement/qualityTestStandardBinding.js";
const { proxy } = getCurrentInstance()
  const { proxy } = getCurrentInstance();
// å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
  // å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
  const rowClassNameCenter = () => "row-center";
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    state: '',
    inspectType: ''
  }
})
const { searchForm } = toRefs(data)
  const data = reactive({
    searchForm: {
      standardNo: "",
      standardName: "",
      state: "",
      inspectType: "",
    },
  });
  const { searchForm } = toRefs(data);
// å·¦ä¾§
const standardTableData = ref([])
const tableLoading = ref(false)
const page = reactive({ current: 1, size: 10, total: 0 })
  // å·¦ä¾§
  const standardTableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({ current: 1, size: 10, total: 0 });
// å·¥åºä¸‹æ‹‰ï¼ˆç”¨äºŽåˆ—表回显)
const processOptions = ref([])
  // å·¥åºä¸‹æ‹‰ï¼ˆç”¨äºŽåˆ—表回显)
  const processOptions = ref([]);
const getProcessList = async () => {
  try {
    const res = await productProcessListPage({ current: 1, size: 1000 })
    if (res?.code === 200) {
      const records = res?.data?.records || []
      processOptions.value = records.map((item) => ({
        label: item.processName || item.name || item.label,
        value: item.id || item.processId || item.value
      }))
  const getProcessList = async () => {
    try {
      const res = await productProcessListPage({ current: 1, size: 1000 });
      if (res?.code === 200) {
        const records = res?.data?.records || [];
        processOptions.value = records.map(item => ({
          label: item.processName || item.name || item.label,
          value: item.id || item.processId || item.value,
        }));
      }
    } catch (error) {
      console.error("获取工序列表失败:", error);
    }
  } catch (error) {
    console.error('获取工序列表失败:', error)
  }
}
  };
const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
  {
    label: '类别',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = { 0: '原材料检验', 1: '过程检验', 2: '出厂检验' }
      return map[val] || val
    }
  },
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  }
  // {
  //   label: '状态',
  //   prop: 'state',
  //   headerSlot: 'stateHeader',
  //   dataType: 'tag',
  //   formatData: (val) => {
  //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
  //     return map[val] || val
  //   },
  //   formatType: (val) => {
  //     if (val == 1) return 'success'
  //     if (val == 2) return 'warning'
  //     return 'info'
  //   }
  // }
])
  const standardColumns = ref([
    {
      label: "标准编号",
      prop: "standardNo",
      dataType: "slot",
      slot: "standardNoCell",
      minWidth: 160,
      align: "center",
      headerSlot: "standardNoHeader",
    },
    {
      label: "标准名称",
      prop: "standardName",
      minWidth: 180,
      align: "center",
      headerSlot: "standardNameHeader",
    },
    {
      label: "类别",
      prop: "inspectType",
      headerSlot: "inspectTypeHeader",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const map = { 0: "原材料检验", 1: "过程检验", 2: "出厂检验" };
        return map[val] || val;
      },
    },
    {
      label: "工序",
      prop: "processId",
      align: "center",
      dataType: "tag",
      formatData: val => {
        const target = processOptions.value.find(
          item => String(item.value) === String(val)
        );
        return target?.label || val;
      },
    },
    {
      label: "备注",
      prop: "remark",
      minWidth: 160,
      align: "center",
    },
    // {
    //   label: '状态',
    //   prop: 'state',
    //   headerSlot: 'stateHeader',
    //   dataType: 'tag',
    //   formatData: (val) => {
    //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
    //     return map[val] || val
    //   },
    //   formatType: (val) => {
    //     if (val == 1) return 'success'
    //     if (val == 2) return 'warning'
    //     return 'info'
    //   }
    // }
  ]);
const currentStandard = ref(null)
  const currentStandard = ref(null);
// å³ä¾§ç»‘定
const bindingTableData = ref([])
const bindingLoading = ref(false)
const bindingSelectedRows = ref([])
const bindingDialogVisible = ref(false)
  // å³ä¾§ç»‘定
  const bindingTableData = ref([]);
  const bindingLoading = ref(false);
  const bindingSelectedRows = ref([]);
  const bindingDialogVisible = ref(false);
// äº§å“æ ‘(用于绑定选择)
const productOptions = ref([])
const selectedProductIds = ref([])
  // äº§å“é€‰æ‹©
  const productSelectDialogVisible = ref(false);
  const selectedProducts = ref([]);
const getProductOptions = async () => {
  // é¿å…é‡å¤è¯·æ±‚
  if (productOptions.value?.length) return
  const res = await productTreeList()
  productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
}
  const openProductSelectDialog = () => {
    productSelectDialogVisible.value = true;
  };
function convertIdToValue(data) {
  return (data || []).map((item) => {
    const { id, children, ...rest } = item
    const newItem = {
      ...rest,
      value: id
    }
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children)
    }
    return newItem
  })
}
  const handleProductSelect = products => {
    // åˆå¹¶å·²é€‰æ‹©çš„产品,避免重复
    const existingIds = new Set(selectedProducts.value.map(p => p.id));
    const newProducts = products.filter(p => !existingIds.has(p.id));
    selectedProducts.value = [...selectedProducts.value, ...newProducts];
  };
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
  const removeSelectedProduct = id => {
    selectedProducts.value = selectedProducts.value.filter(p => p.id !== id);
  };
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
  const handleQuery = () => {
    page.current = 1;
    getStandardList();
  };
const getStandardList = () => {
  tableLoading.value = true
  qualityTestStandardListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
    state: 1
  })
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
  const handlePagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getStandardList();
  };
  const getStandardList = () => {
    tableLoading.value = true;
    qualityTestStandardListPage({
      ...searchForm.value,
      current: page.current,
      size: page.size,
      state: 1,
    })
    .finally(() => {
      tableLoading.value = false
    })
}
      .then(res => {
        const records = res?.data?.records || [];
        standardTableData.value = records;
        page.total = res?.data?.total || records.length;
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
// è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  // è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨
  const handleTableRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
  // å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
  const handleStandardRowClick = row => {
    currentStandard.value = row;
    loadBindingList();
  };
const loadBindingList = () => {
  if (!currentStandard.value?.id) {
    bindingTableData.value = []
    return
  }
  bindingLoading.value = true
  qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
    .then((res) => {
      const base = res?.data || []
      // å°†å½“前标准的工序和备注带到绑定列表中展示
      bindingTableData.value = base.map((item) => ({
        ...item,
        processId: currentStandard.value?.processId,
        remark: currentStandard.value?.remark
      }))
    })
    .finally(() => {
      bindingLoading.value = false
    })
}
  const loadBindingList = () => {
    if (!currentStandard.value?.id) {
      bindingTableData.value = [];
      return;
    }
    bindingLoading.value = true;
    qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
      .then(res => {
        const base = res?.data || [];
        // å°†å½“前标准的工序和备注带到绑定列表中展示
        bindingTableData.value = base.map(item => ({
          ...item,
          processId: currentStandard.value?.processId,
          remark: currentStandard.value?.remark,
        }));
      })
      .finally(() => {
        bindingLoading.value = false;
      });
  };
const handleBindingSelectionChange = (selection) => {
  bindingSelectedRows.value = selection
}
  const handleBindingSelectionChange = selection => {
    bindingSelectedRows.value = selection;
  };
const openBindingDialog = () => {
  if (!currentStandard.value?.id) return
  selectedProductIds.value = []
  getProductOptions()
  bindingDialogVisible.value = true
}
  const openBindingDialog = () => {
    if (!currentStandard.value?.id) return;
    selectedProducts.value = [];
    bindingDialogVisible.value = true;
  };
const closeBindingDialog = () => {
  bindingDialogVisible.value = false
}
  const closeBindingDialog = () => {
    bindingDialogVisible.value = false;
    selectedProducts.value = [];
  };
const submitBinding = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  const ids = (selectedProductIds.value || []).filter(Boolean)
  if (!ids.length) {
    proxy.$message.warning('请选择产品')
    return
  }
  const payload = ids.map((pid) => ({
    productId: pid,
    testStandardId
  }))
  await qualityTestStandardBindingAdd(payload)
  proxy.$message.success('添加成功')
  bindingDialogVisible.value = false
  loadBindingList()
}
  const submitBinding = async () => {
    const testStandardId = currentStandard.value?.id;
    if (!testStandardId) return;
    const ids = (selectedProducts.value || []).map(p => p.id).filter(Boolean);
    if (!ids.length) {
      ElMessage.warning("请选择产品");
      return;
    }
    const payload = ids.map(pid => ({
      productId: pid,
      testStandardId,
    }));
    await qualityTestStandardBindingAdd(payload);
    ElMessage.success("添加成功");
    bindingDialogVisible.value = false;
    selectedProducts.value = [];
    loadBindingList();
  };
const handleUnbind = async (row) => {
  const id = row?.id ?? row?.qualityTestStandardBindingId
  if (id == null || id === '') return
  try {
    await ElMessageBox.confirm('确认删除该绑定?', '提示', { type: 'warning' })
  } catch {
    return
  }
  try {
    await qualityTestStandardBindingDel([id])
    proxy.$message.success('删除成功')
    loadBindingList()
  } catch (err) {
    console.error('删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
  }
}
  const handleUnbind = async row => {
    const id = row?.id ?? row?.qualityTestStandardBindingId;
    if (id == null || id === "") return;
    try {
      await ElMessageBox.confirm("确认删除该绑定?", "提示", { type: "warning" });
    } catch {
      return;
    }
    try {
      await qualityTestStandardBindingDel([id]);
      proxy.$message.success("删除成功");
      loadBindingList();
    } catch (err) {
      console.error("删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    }
  };
const handleBatchUnbind = async () => {
  if (!bindingSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = bindingSelectedRows.value
    .map((i) => i?.id ?? i?.qualityTestStandardBindingId)
    .filter((id) => id != null && id !== '')
  if (!ids.length) {
    proxy.$message.warning('选中数据缺少有效 id')
    return
  }
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  try {
    await qualityTestStandardBindingDel(ids)
    proxy.$message.success('删除成功')
    loadBindingList()
  } catch (err) {
    console.error('批量删除绑定失败:', err)
    proxy.$message?.error(err?.message || '删除失败')
  }
}
  const handleBatchUnbind = async () => {
    if (!bindingSelectedRows.value.length) {
      proxy.$message.warning("请选择数据");
      return;
    }
    const ids = bindingSelectedRows.value
      .map(i => i?.id ?? i?.qualityTestStandardBindingId)
      .filter(id => id != null && id !== "");
    if (!ids.length) {
      proxy.$message.warning("选中数据缺少有效 id");
      return;
    }
    try {
      await ElMessageBox.confirm(
        "选中的内容将被删除,是否确认删除?",
        "删除提示",
        { type: "warning" }
      );
    } catch {
      return;
    }
    try {
      await qualityTestStandardBindingDel(ids);
      proxy.$message.success("删除成功");
      loadBindingList();
    } catch (err) {
      console.error("批量删除绑定失败:", err);
      proxy.$message?.error(err?.message || "删除失败");
    }
  };
onMounted(() => {
  getStandardList()
  getProcessList()
})
  onMounted(() => {
    getStandardList();
    getProcessList();
  });
</script>
<style scoped>
.metric-binding {
  padding: 0;
}
  .metric-binding {
    padding: 0;
  }
.metric-binding-row {
  width: 100%;
}
  .metric-binding-row {
    width: 100%;
  }
.metric-binding-row .left-col,
.metric-binding-row .right-col {
  margin-bottom: 16px;
}
  .metric-binding-row .left-col,
  .metric-binding-row .right-col {
    margin-bottom: 16px;
  }
.metric-binding-row .panel {
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
  height: 100%;
  min-height: 400px;
}
  .metric-binding-row .panel {
    background: #ffffff;
    padding: 16px;
    box-sizing: border-box;
    height: 100%;
    min-height: 400px;
  }
.left-panel,
.right-panel {
  height: 100%;
}
  .left-panel,
  .right-panel {
    height: 100%;
  }
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
  .toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
.toolbar-right {
  flex-shrink: 0;
}
  .toolbar-right {
    flex-shrink: 0;
  }
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
  .right-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 10px;
  }
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
  .right-header .title {
    font-size: 16px;
    font-weight: 600;
  }
.right-header .desc {
  font-size: 13px;
  color: #666;
}
  .right-header .desc {
    font-size: 13px;
    color: #666;
  }
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
  .right-toolbar {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-bottom: 10px;
  }
.link-text {
  color: #409eff;
  cursor: default;
}
  .link-text {
    color: #409eff;
    cursor: default;
  }
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
  .clickable-link {
    color: #409eff;
    cursor: pointer;
  }
.clickable-link:hover {
  text-decoration: underline;
}
  .clickable-link:hover {
    text-decoration: underline;
  }
:deep(.row-center td) {
  text-align: center !important;
}
  :deep(.row-center td) {
    text-align: center !important;
  }
/* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
  /* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
  :deep(.center-table .el-table__header-wrapper th .cell) {
    text-align: center !important;
  }
  :deep(.center-table .el-table__body-wrapper td .cell) {
    text-align: center !important;
  }
/* PIMTable è¡¨å¤´å±…中 */
:deep(.lims-table .pim-table-header-cell) {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
  /* PIMTable è¡¨å¤´å±…中 */
  :deep(.lims-table .pim-table-header-cell) {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
:deep(.lims-table .pim-table-header-title) {
  text-align: center;
  width: 100%;
}
  :deep(.lims-table .pim-table-header-title) {
    text-align: center;
    width: 100%;
  }
:deep(.lims-table .pim-table-header-extra) {
  width: 100%;
  margin-top: 4px;
}
  :deep(.lims-table .pim-table-header-extra) {
    width: 100%;
    margin-top: 4px;
  }
</style>