zhangwencui
2026-04-20 f12d684876ffbe90fa95c3113b1e6c4308ae4615
参数和工序部分
已添加3个文件
已修改7个文件
已删除2个文件
6120 ■■■■ 文件已修改
src/api/basicData/parameterMaintenance.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 854 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ProcessParamListDialog.vue 656 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/parameterMaintenance/index.vue 790 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 2031 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 1277 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/parameterMaintenance.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
// å‚数维护页面接口
import request from "@/utils/request";
// æŸ¥è¯¢å‚数列表
export function parameterListPage(query) {
  return request({
    url: "/basic/parameter/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žå‚æ•°
export function addParameter(data) {
  return request({
    url: "/basic/parameter/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑参数
export function updateParameter(data) {
  return request({
    url: "/basic/parameter/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å‚æ•°
export function delParameter(ids) {
  return request({
    url: "/basic/parameter/del",
    method: "delete",
    data: Array.isArray(ids) ? ids : [ids],
  });
}
// èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨
export function getProductTypes() {
  return request({
    url: "/basic/product/typeList",
    method: "get",
  });
}
// æ–°å¢žåŸºç¡€å‚æ•°
export function addBaseParam(data) {
  return request({
    url: "/technologyParam/add",
    method: "post",
    data: data,
  });
}
// ç¼–辑基础参数
export function editBaseParam(data) {
  return request({
    url: "/technologyParam/edit",
    method: "put",
    data: data,
  });
}
// æŸ¥è¯¢åŸºç¡€å‚数列表
export function getBaseParamList(query) {
  return request({
    url: "/technologyParam/list",
    method: "get",
    params: query,
  });
}
// åˆ é™¤åŸºç¡€å‚æ•°
export function removeBaseParam(ids) {
  return request({
    url: `/technologyParam/remove/` + ids,
    method: "delete",
  });
}
src/api/productionManagement/processRouteItem.js
@@ -36,3 +36,34 @@
    method: "delete",
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨
export function getProcessParamList(query) {
  return request({
    url: `/ProcessRouteItemParam/pageList`,
    method: "get",
    params: query,
  });
}
// å·¥è‰ºè·¯çº¿å‚数新增
export function addProcessRouteItemParam(data) {
  return request({
    url: "/ProcessRouteItemParam/save",
    method: "post",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数修改
export function editProcessRouteItemParam(data) {
  return request({
    url: "/ProcessRouteItemParam/edit",
    method: "put",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数删除
export function delProcessRouteItemParam(id) {
  return request({
    url: `/ProcessRouteItemParam/remove/${id}`,
    method: "delete",
  });
}
src/api/productionManagement/productProcessRoute.js
@@ -52,3 +52,34 @@
    data,
  });
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨-生产订单
export function findProcessParamListOrder(query) {
  return request({
    url: `/productionOrderRouteItemParam/list`,
    method: "get",
    params: query,
  });
}
// å·¥è‰ºè·¯çº¿å‚数新增-生产订单
export function addProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/add",
    method: "post",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数修改-生产订单
export function editProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/update",
    method: "put",
    data: data,
  });
}
// å·¥è‰ºè·¯çº¿å‚数删除-生产订单
export function delProcessRouteItemParamOrder(id) {
  return request({
    url: `/productionOrderRouteItemParam/delete/${id}`,
    method: "delete",
  });
}
src/api/productionManagement/productStructure.js
@@ -8,11 +8,33 @@
    method: "get",
  });
}
export function add(data) {
  return request({
    url: "/productStructure",
    url: "/productStructure/" + data.bomId,
    method: "post",
    data: data,
    data: data.children,
  });
}
// export function add(data) {
//   return request({
//     url: "/productStructure",
//     method: "post",
//     data: data,
//   });
// }
// åˆ†é¡µæŸ¥è¯¢-产品订单
export function queryList2(id) {
  return request({
    url: "/productionOrderStructure/getBomStructs/" + id,
    method: "get",
  });
}
export function add2(data) {
  return request({
    url: "/productionOrderStructure/addOrUpdateBomStructs/" + data.orderId,
    method: "put",
    data: data.children,
  });
}
src/api/productionManagement/productionProcess.js
@@ -12,7 +12,7 @@
export function processList(query) {
  return request({
    url: "/productProcess/list",
    url: "/technologyOperation/listPage",
    method: "get",
    params: query,
  });
@@ -20,7 +20,7 @@
export function add(data) {
  return request({
    url: "/productProcess",
    url: "/technologyOperation/",
    method: "post",
    data: data,
  });
@@ -28,26 +28,27 @@
export function del(data) {
  return request({
    url: '/productProcess/batchDelete',
    method: 'delete',
    url: "/technologyOperation/batchDelete",
    method: "delete",
    data: data,
  })
  });
}
export function update(data) {
  return request({
    url: '/productProcess/update',
    method: 'put',
    url: "/technologyOperation/update",
    method: "put",
    data: data,
  })
  });
}
// å·¥åºæŸ¥è¯¢
export function list() {
    return request({
        url: "/productProcess/list",
        method: "get",
    });
export function list(query) {
  return request({
    url: "/technologyOperation/listPage",
    method: "get",
    params: query,
  });
}
// å¯¼å…¥æ•°æ®
@@ -66,4 +67,39 @@
    method: "post",
    responseType: "blob",
  });
}
}
// èŽ·å–å·¥åºå‚æ•°åˆ—è¡¨
export function getProcessParamList(processId, params) {
  return request({
    url: `/productProcessParam/list/${processId}`,
    method: "get",
    params,
  });
}
// æ·»åŠ å·¥åºå‚æ•°
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
@@ -1,83 +1,75 @@
<template>
  <el-table
    ref="multipleTable"
    v-loading="tableLoading"
    :border="border"
    :data="tableData"
    :header-cell-style="mergedHeaderCellStyle"
    :height="height"
    :highlight-current-row="highlightCurrentRow"
    :row-class-name="rowClassName"
    :row-style="rowStyle"
    :row-key="rowKey"
    :style="tableStyle"
    tooltip-effect="dark"
    :expand-row-keys="expandRowKeys"
    :show-summary="isShowSummary"
    :summary-method="summaryMethod"
    @row-click="rowClick"
    @current-change="currentChange"
    @selection-change="handleSelectionChange"
    @expand-change="expandChange"
    class="lims-table"
  >
    <el-table-column
      align="center"
      type="selection"
      width="55"
      v-if="isSelection"
    />
    <el-table-column align="center" label="序号" type="index" width="60" />
    <el-table-column
      v-for="(item, index) in column"
      :key="index"
      :column-key="item.columnKey"
      :filter-method="item.filterHandler"
      :filter-multiple="item.filterMultiple"
      :filtered-value="item.filteredValue"
      :filters="item.filters"
      :fixed="item.fixed"
      :label="item.label"
      :prop="item.prop"
      :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
      :align="item.align"
      :sortable="!!item.sortable"
      :type="item.type"
      :width="item.width"
      :minWidth="item.minWidth"
    >
  <el-table ref="multipleTable"
            v-loading="tableLoading"
            :border="border"
            :data="tableData"
            :header-cell-style="mergedHeaderCellStyle"
            :height="height"
            :highlight-current-row="highlightCurrentRow"
            :row-class-name="rowClassName"
            :row-style="rowStyle"
            :row-key="rowKey"
            :style="tableStyle"
            tooltip-effect="dark"
            :expand-row-keys="expandRowKeys"
            :show-summary="isShowSummary"
            :summary-method="summaryMethod"
            @row-click="rowClick"
            @current-change="currentChange"
            @selection-change="handleSelectionChange"
            @expand-change="expandChange"
            class="lims-table">
    <el-table-column align="center"
                     type="selection"
                     width="55"
                     v-if="isSelection" />
    <el-table-column align="center"
                     label="序号"
                     type="index"
                     width="60" />
    <el-table-column v-for="(item, index) in column"
                     :key="index"
                     :column-key="item.columnKey"
                     :filter-method="item.filterHandler"
                     :filter-multiple="item.filterMultiple"
                     :filtered-value="item.filteredValue"
                     :filters="item.filters"
                     :fixed="item.fixed"
                     :label="item.label"
                     :prop="item.prop"
                     :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
                     :align="item.align"
                     :sortable="!!item.sortable"
                     :type="item.type"
                     :width="item.width"
                     :minWidth="item.minWidth">
      <template #header="scope">
        <div class="pim-table-header-cell" :class="{ 'has-extra': item.headerSlot }">
        <div class="pim-table-header-cell"
             :class="{ 'has-extra': item.headerSlot }">
          <div class="pim-table-header-title">
            {{ item.label }}
          </div>
          <div v-if="item.headerSlot" class="pim-table-header-extra">
            <slot :name="item.headerSlot" :column="scope.column" />
          <div v-if="item.headerSlot"
               class="pim-table-header-extra">
            <slot :name="item.headerSlot"
                  :column="scope.column" />
          </div>
        </div>
      </template>
      <template
        v-if="item.hasOwnProperty('colunmTemplate')"
        #[item.colunmTemplate]="scope"
      >
        <slot
          v-if="item.theadSlot"
          :name="item.theadSlot"
          :index="scope.$index"
          :row="scope.row"
        />
      <template v-if="item.hasOwnProperty('colunmTemplate')"
                #[item.colunmTemplate]="scope">
        <slot v-if="item.theadSlot"
              :name="item.theadSlot"
              :index="scope.$index"
              :row="scope.row" />
      </template>
      <template #default="scope">
        <!-- æ’æ§½ -->
        <div v-if="item.dataType == 'slot'">
          <slot
            v-if="item.slot"
            :index="scope.$index"
            :name="item.slot"
            :row="scope.row"
          />
          <slot v-if="item.slot"
                :index="scope.$index"
                :name="item.slot"
                :row="scope.row" />
        </div>
        <!-- è¿›åº¦æ¡ -->
        <div v-else-if="item.dataType == 'progress'">
@@ -85,128 +77,111 @@
        </div>
        <!-- å›¾ç‰‡ -->
        <div v-else-if="item.dataType == 'image'">
          <img
            :src="javaApi + '/img/' + scope.row[item.prop]"
            alt=""
            style="width: 40px; height: 40px; margin-top: 10px"
          />
          <img :src="javaApi + '/img/' + scope.row[item.prop]"
               alt=""
               style="width: 40px; height: 40px; margin-top: 10px" />
        </div>
        <!-- tag -->
        <div v-else-if="item.dataType == 'tag'">
          <el-tag
            v-if="
          <el-tag v-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'string'
            "
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(scope.row[item.prop], item.formatType)"
          >
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
          <el-tag
            v-for="(tag, index) in dataTypeFn(
          <el-tag v-for="(tag, index) in dataTypeFn(
              scope.row[item.prop],
              item.formatData
            )"
            v-else-if="
                  v-else-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'object'
            "
            :key="index"
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(tag, item.formatType)"
          >
                  :key="index"
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :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)"
            :type="formatType(scope.row[item.prop], item.formatType)"
          >
          <el-tag v-else
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'" @click.stop>
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="isOperationDisabled(o, scope.row)"
              :plain="o.plain"
              type="primary"
              :style="{
        <div v-else-if="item.dataType == 'action'"
             @click.stop>
          <template v-for="(o, key) in item.operation"
                    :key="key">
            <el-button v-show="o.type != 'upload'"
                       v-if="o.showHide ? o.showHide(scope.row) : true"
                       :disabled="isOperationDisabled(o, scope.row)"
                       :plain="o.plain"
                       type="primary"
                       :style="{
                color: getOperationColor(o, scope.row),
                fontWeight: 'bold',
              }"
              link
              @click.stop="o.clickFun(scope.row)"
              :key="key"
            >
                       link
                       @click.stop="o.clickFun(scope.row)"
                       :key="key">
              {{ o.name }}
            </el-button>
            <el-upload
              :action="
            <el-upload :action="
                javaApi +
                o.url +
                '?id=' +
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
              ref="uploadRef"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="isOperationDisabled(o, scope.row)"
              :accept="
                       ref="uploadRef"
                       :multiple="o.multiple ? o.multiple : false"
                       :limit="1"
                       :disabled="isOperationDisabled(o, scope.row)"
                       :accept="
                o.accept
                  ? o.accept
                  : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
              "
              v-if="o.type == 'upload'"
              style="display: inline-block; width: 50px"
              v-show="o.showHide ? o.showHide(scope.row) : true"
              :headers="uploadHeader"
              :before-upload="(file) => beforeUpload(file, scope.$index)"
              :on-change="
                       v-if="o.type == 'upload'"
                       style="display: inline-block; width: 50px"
                       v-show="o.showHide ? o.showHide(scope.row) : true"
                       :headers="uploadHeader"
                       :before-upload="(file) => beforeUpload(file, scope.$index)"
                       :on-change="
                (file, fileList) => handleChange(file, fileList, scope.$index)
              "
              :on-error="
                       :on-error="
                (error, file, fileList) =>
                  onError(error, file, fileList, scope.$index)
              "
              :on-success="
                       :on-success="
                (response, file, fileList) =>
                  handleSuccessUp(response, file, fileList, scope.$index)
              "
              :on-exceed="onExceed"
              :show-file-list="false"
            >
              <el-button
                link
                type="primary"
                :disabled="isOperationDisabled(o, scope.row)"
                :style="{
                       :on-exceed="onExceed"
                       :show-file-list="false">
              <el-button link
                         type="primary"
                         :disabled="isOperationDisabled(o, scope.row)"
                         :style="{
                  color: getOperationColor(o, scope.row),
                }"
                >{{ o.name }}</el-button
              >
                }">{{ o.name }}</el-button>
            </el-upload>
          </template>
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div
          v-else-if="item.dataType == 'link'"
          class="cell link"
          style="width: 100%"
          @click="goLink(scope.row, item.linkMethod)"
        >
        <div v-else-if="item.dataType == 'link'"
             class="cell link"
             style="width: 100%"
             @click="goLink(scope.row, item.linkMethod)">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
        </div>
        <!-- é»˜è®¤çº¯å±•示数据 -->
        <div v-else class="cell" style="width: 100%">
        <div v-else
             class="cell"
             style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{
            formatters(scope.row[item.prop], item.formatData)
@@ -215,326 +190,333 @@
      </template>
    </el-table-column>
  </el-table>
  <pagination
        v-if="isShowPagination"
    :total="page.total"
    :layout="page.layout"
    :page="page.current"
    :limit="page.size"
    @pagination="paginationSearch"
  />
  <pagination v-if="isShowPagination"
              :total="page.total"
              :layout="page.layout"
              :page="page.current"
              :limit="page.size"
              @pagination="paginationSearch" />
</template>
<script setup>
import pagination from "./Pagination.vue";
import { computed, ref, inject, getCurrentInstance } from "vue";
import { ElMessage } from "element-plus";
  import pagination from "./Pagination.vue";
  import { computed, ref, inject, getCurrentInstance } from "vue";
  import { ElMessage } from "element-plus";
// èŽ·å–å…¨å±€çš„ uploadHeader
const { proxy } = getCurrentInstance();
const uploadHeader = proxy.uploadHeader;
const javaApi = proxy.javaApi;
  // èŽ·å–å…¨å±€çš„ uploadHeader
  const { proxy } = getCurrentInstance();
  const uploadHeader = proxy.uploadHeader;
  const javaApi = proxy.javaApi;
const emit = defineEmits(["pagination", "expand-change", "selection-change", "row-click"]);
  const emit = defineEmits([
    "pagination",
    "expand-change",
    "selection-change",
    "row-click",
  ]);
// Filters
const typeFn = (val, row) => {
  return typeof val === "function" ? val(row) : val;
};
  // Filters
  const typeFn = (val, row) => {
    return typeof val === "function" ? val(row) : val;
  };
const formatters = (val, format) => {
  return typeof format === "function" ? format(val) : val;
};
  const formatters = (val, format) => {
    return typeof format === "function" ? format(val) : val;
  };
// Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
const props = defineProps({
  tableLoading: {
    type: Boolean,
    default: false,
  },
  height: {
    type: [Number, String],
    default: "calc(100vh - 22em)",
  },
  expandRowKeys: {
    type: Array,
    default: () => [],
  },
  summaryMethod: {
    type: Function,
    default: () => {},
  },
  rowClick: {
    type: Function,
    default: () => {},
  },
  currentChange: {
    type: Function,
    default: () => {},
  },
  border: {
    type: Boolean,
    default: true,
  },
  isSelection: {
    type: Boolean,
    default: false,
  },
    isShowPagination: {
    type: Boolean,
    default: true,
  },
  isShowSummary: {
    type: Boolean,
    default: false,
  },
  highlightCurrentRow: {
    type: Boolean,
    default: false,
  },
  headerCellStyle: {
    type: Object,
    default: () => ({}),
  },
  column: {
    type: Array,
    default: () => [],
  },
  rowClassName: {
    type: Function,
    default: () => "",
  },
  rowStyle: {
    type: [Object, Function],
    default: () => ({}),
  },
  tableData: {
    type: Array,
    default: () => [],
  },
  rowKey: {
    type: String,
    default: 'id',
  },
  page: {
    type: Object,
    default: () => ({
      total: 0,
      current: 0,
      size: 10,
      layout: "total, sizes, prev, pager, next, jumper",
    }),
  },
  total: {
    type: Number,
    default: 0,
  },
  tableStyle: {
    type: [String, Object],
    default: () => ({ width: "100%" }),
  },
});
  // Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
  const props = defineProps({
    tableLoading: {
      type: Boolean,
      default: false,
    },
    height: {
      type: [Number, String],
      default: "calc(100vh - 22em)",
    },
    expandRowKeys: {
      type: Array,
      default: () => [],
    },
    summaryMethod: {
      type: Function,
      default: () => {},
    },
    rowClick: {
      type: Function,
      default: () => {},
    },
    currentChange: {
      type: Function,
      default: () => {},
    },
    border: {
      type: Boolean,
      default: true,
    },
    isSelection: {
      type: Boolean,
      default: false,
    },
    isShowPagination: {
      type: Boolean,
      default: true,
    },
    isShowSummary: {
      type: Boolean,
      default: false,
    },
    highlightCurrentRow: {
      type: Boolean,
      default: false,
    },
    headerCellStyle: {
      type: Object,
      default: () => ({}),
    },
    column: {
      type: Array,
      default: () => [],
    },
    rowClassName: {
      type: Function,
      default: () => "",
    },
    rowStyle: {
      type: [Object, Function],
      default: () => ({}),
    },
    tableData: {
      type: Array,
      default: () => [],
    },
    rowKey: {
      type: String,
      default: "id",
    },
    page: {
      type: Object,
      default: () => ({
        total: 0,
        current: 0,
        size: 10,
        layout: "total, sizes, prev, pager, next, jumper",
      }),
    },
    total: {
      type: Number,
      default: 0,
    },
    tableStyle: {
      type: [String, Object],
      default: () => ({ width: "100%" }),
    },
  });
const mergedHeaderCellStyle = computed(() => ({
  background: "var(--surface-soft)",
  color: "var(--text-secondary)",
  fontWeight: 600,
  ...props.headerCellStyle,
}));
  const mergedHeaderCellStyle = computed(() => ({
    background: "var(--surface-soft)",
    color: "var(--text-secondary)",
    fontWeight: 600,
    ...props.headerCellStyle,
  }));
// Data
const uploadRefs = ref([]);
const currentFiles = ref({});
const uploadKeys = ref({});
  // Data
  const uploadRefs = ref([]);
  const currentFiles = ref({});
  const uploadKeys = ref({});
const indexMethod = (index) => {
  return (props.page.current - 1) * props.page.size + index + 1;
};
  const indexMethod = index => {
    return (props.page.current - 1) * props.page.size + index + 1;
  };
// ç‚¹å‡» link äº‹ä»¶
const goLink = (row, linkMethod) => {
  if (!linkMethod) {
    return ElMessage.warning("请配置 link äº‹ä»¶");
  }
  const parentMethod = getParentMethod(linkMethod);
  if (typeof parentMethod === "function") {
    parentMethod(row);
  } else {
    console.warn(`父组件中未找到方法: ${linkMethod}`);
  }
};
// èŽ·å–çˆ¶ç»„ä»¶æ–¹æ³•ï¼ˆç¤ºä¾‹å®žçŽ°ï¼‰
const getParentMethod = (methodName) => {
  const parentMethods = inject("parentMethods", {});
  return parentMethods[methodName];
};
const dataTypeFn = (val, format) => {
  if (typeof format === "function") {
    return format(val);
  } else return val;
};
const validTagTypes = ["primary", "success", "info", "warning", "danger"];
const formatType = (val, format) => {
  const type = typeof format === "function" ? format(val) : undefined;
  return validTagTypes.includes(type) ? type : undefined;
};
const isOperationDisabled = (operation, row) => {
  if (!operation?.disabled) return false;
  return typeof operation.disabled === "function"
    ? !!operation.disabled(row)
    : !!operation.disabled;
};
const parseHexToRgb = (hex) => {
  const normalized = String(hex || "").trim().replace("#", "");
  if (normalized.length === 3) {
    const r = parseInt(normalized[0] + normalized[0], 16);
    const g = parseInt(normalized[1] + normalized[1], 16);
    const b = parseInt(normalized[2] + normalized[2], 16);
    if ([r, g, b].some((n) => Number.isNaN(n))) return null;
    return { r, g, b };
  }
  if (normalized.length === 6 || normalized.length === 8) {
    const r = parseInt(normalized.slice(0, 2), 16);
    const g = parseInt(normalized.slice(2, 4), 16);
    const b = parseInt(normalized.slice(4, 6), 16);
    if ([r, g, b].some((n) => Number.isNaN(n))) return null;
    return { r, g, b };
  }
  return null;
};
const fadeColor = (color, alpha = 0.35) => {
  const c = String(color || "").trim();
  if (!c) return undefined;
  if (c.startsWith("#")) {
    const rgb = parseHexToRgb(c);
    if (!rgb) return c;
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
  }
  const rgbMatch = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i);
  if (rgbMatch) {
    const r = Number(rgbMatch[1]);
    const g = Number(rgbMatch[2]);
    const b = Number(rgbMatch[3]);
    if ([r, g, b].some((n) => Number.isNaN(n))) return c;
    return `rgba(${r}, ${g}, ${b}, ${alpha})`;
  }
  if (c.includes("--el-color-primary")) {
    return "var(--el-color-primary-light-5)";
  }
  if (c.includes("--el-color-danger")) {
    return "var(--el-color-danger-light-5)";
  }
  return "var(--el-text-color-disabled)";
};
const getOperationColor = (operation, row) => {
  const baseColor =
    operation?.name === "删除" || operation?.name === "delete"
      ? "#D93025"
      : operation?.name === "详情"
      ? "#67C23A"
      : operation?.color || "var(--el-color-primary)";
  if (isOperationDisabled(operation, row)) {
    return fadeColor(baseColor, 0.35);
  }
  return baseColor;
};
// æ–‡ä»¶å˜åŒ–处理
const handleChange = (file, fileList, index) => {
  if (fileList.length > 1) {
    const earliestFile = fileList[0];
    uploadRefs.value[index]?.handleRemove(earliestFile);
  }
  currentFiles.value[index] = file;
};
// æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
const beforeUpload = (rawFile, index) => {
  currentFiles.value[index] = {};
  if (rawfile.size > 1024 * 1024 * 10 * 10) {
    ElMessage.error("上传文件不超过10M");
    return false;
  }
  return true;
};
// ä¸Šä¼ æˆåŠŸ
const handleSuccessUp = (response, file, fileList, index) => {
  if (response.code == 200) {
    if (uploadRefs[index]) {
      uploadRefs[index].clearFiles();
  // ç‚¹å‡» link äº‹ä»¶
  const goLink = (row, linkMethod) => {
    if (!linkMethod) {
      return ElMessage.warning("请配置 link äº‹ä»¶");
    }
    currentFiles[index] = file;
    ElMessage.success("上传成功");
    resetUploadComponent(index);
  } else {
    ElMessage.error(response.message);
  }
};
    const parentMethod = getParentMethod(linkMethod);
    if (typeof parentMethod === "function") {
      parentMethod(row);
    } else {
      console.warn(`父组件中未找到方法: ${linkMethod}`);
    }
  };
const resetUploadComponent = (index) => {
  uploadKeys[index] = Date.now();
};
  // èŽ·å–çˆ¶ç»„ä»¶æ–¹æ³•ï¼ˆç¤ºä¾‹å®žçŽ°ï¼‰
  const getParentMethod = methodName => {
    const parentMethods = inject("parentMethods", {});
    return parentMethods[methodName];
  };
// ä¸Šä¼ å¤±è´¥
const onError = (error, file, fileList, index) => {
  ElMessage.error("文件上传失败,请重试");
  if (uploadRefs.value[index]) {
    uploadRefs.value[index].clearFiles();
  }
};
  const dataTypeFn = (val, format) => {
    if (typeof format === "function") {
      return format(val);
    } else return val;
  };
  const validTagTypes = ["primary", "success", "info", "warning", "danger"];
// æ–‡ä»¶æ•°é‡è¶…限提示
const onExceed = () => {
  ElMessage.warning("超出文件个数");
};
  const formatType = (val, format) => {
    const type = typeof format === "function" ? format(val) : undefined;
    return validTagTypes.includes(type) ? type : undefined;
  };
const paginationSearch = ({ page, limit }) => {
  emit("pagination", { page: page, limit: limit });
};
  const isOperationDisabled = (operation, row) => {
    if (!operation?.disabled) return false;
    return typeof operation.disabled === "function"
      ? !!operation.disabled(row)
      : !!operation.disabled;
  };
const rowClick = (row) => {
  emit("row-click", row);
};
  const parseHexToRgb = hex => {
    const normalized = String(hex || "")
      .trim()
      .replace("#", "");
    if (normalized.length === 3) {
      const r = parseInt(normalized[0] + normalized[0], 16);
      const g = parseInt(normalized[1] + normalized[1], 16);
      const b = parseInt(normalized[2] + normalized[2], 16);
      if ([r, g, b].some(n => Number.isNaN(n))) return null;
      return { r, g, b };
    }
    if (normalized.length === 6 || normalized.length === 8) {
      const r = parseInt(normalized.slice(0, 2), 16);
      const g = parseInt(normalized.slice(2, 4), 16);
      const b = parseInt(normalized.slice(4, 6), 16);
      if ([r, g, b].some(n => Number.isNaN(n))) return null;
      return { r, g, b };
    }
    return null;
  };
const expandChange = (row, expandedRows) => {
  emit("expand-change", row, expandedRows);
};
  const fadeColor = (color, alpha = 0.35) => {
    const c = String(color || "").trim();
    if (!c) return undefined;
    if (c.startsWith("#")) {
      const rgb = parseHexToRgb(c);
      if (!rgb) return c;
      return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
    }
    const rgbMatch = c.match(
      /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i
    );
    if (rgbMatch) {
      const r = Number(rgbMatch[1]);
      const g = Number(rgbMatch[2]);
      const b = Number(rgbMatch[3]);
      if ([r, g, b].some(n => Number.isNaN(n))) return c;
      return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }
    if (c.includes("--el-color-primary")) {
      return "var(--el-color-primary-light-5)";
    }
    if (c.includes("--el-color-danger")) {
      return "var(--el-color-danger-light-5)";
    }
    return "var(--el-text-color-disabled)";
  };
const handleSelectionChange = (newSelection) => {
  emit("selection-change", newSelection);
};
  const getOperationColor = (operation, row) => {
    const baseColor =
      operation?.name === "删除" || operation?.name === "delete"
        ? "#D93025"
        : operation?.name === "详情"
        ? "#67C23A"
        : operation?.color || "var(--el-color-primary)";
    if (isOperationDisabled(operation, row)) {
      return fadeColor(baseColor, 0.35);
    }
    return baseColor;
  };
  // æ–‡ä»¶å˜åŒ–处理
  const handleChange = (file, fileList, index) => {
    if (fileList.length > 1) {
      const earliestFile = fileList[0];
      uploadRefs.value[index]?.handleRemove(earliestFile);
    }
    currentFiles.value[index] = file;
  };
  // æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
  const beforeUpload = (rawFile, index) => {
    currentFiles.value[index] = {};
    if (rawfile.size > 1024 * 1024 * 10 * 10) {
      ElMessage.error("上传文件不超过10M");
      return false;
    }
    return true;
  };
  // ä¸Šä¼ æˆåŠŸ
  const handleSuccessUp = (response, file, fileList, index) => {
    if (response.code == 200) {
      if (uploadRefs[index]) {
        uploadRefs[index].clearFiles();
      }
      currentFiles[index] = file;
      ElMessage.success("上传成功");
      resetUploadComponent(index);
    } else {
      ElMessage.error(response.message);
    }
  };
  const resetUploadComponent = index => {
    uploadKeys[index] = Date.now();
  };
  // ä¸Šä¼ å¤±è´¥
  const onError = (error, file, fileList, index) => {
    ElMessage.error("文件上传失败,请重试");
    if (uploadRefs.value[index]) {
      uploadRefs.value[index].clearFiles();
    }
  };
  // æ–‡ä»¶æ•°é‡è¶…限提示
  const onExceed = () => {
    ElMessage.warning("超出文件个数");
  };
  const paginationSearch = ({ page, limit }) => {
    emit("pagination", { page: page, limit: limit });
  };
  const rowClick = row => {
    emit("row-click", row);
  };
  const expandChange = (row, expandedRows) => {
    emit("expand-change", row, expandedRows);
  };
  const handleSelectionChange = newSelection => {
    emit("selection-change", newSelection);
  };
</script>
<style scoped lang="scss">
.lims-table {
  border: 1px solid var(--surface-border);
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.9);
}
  .lims-table {
    border: 1px solid var(--surface-border);
    border-radius: 18px;
    background: rgba(255, 255, 255, 0.9);
  }
.cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-right: 0 !important;
  padding-left: 0 !important;
}
  .cell {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right: 0 !important;
    padding-left: 0 !important;
  }
.pim-table-header-extra :deep(.el-input),
.pim-table-header-extra :deep(.el-select) {
  width: 100%;
}
  .pim-table-header-extra :deep(.el-input),
  .pim-table-header-extra :deep(.el-select) {
    width: 100%;
  }
.pim-table-header-title {
  font-weight: 600;
}
  .pim-table-header-title {
    font-weight: 600;
  }
</style>
src/components/ProcessParamListDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,656 @@
<template>
  <el-dialog v-model="visible"
             :title="title"
             width="800px"
             destroy-on-close>
    <div class="param-list-container">
      <div class="params-header">
        <span>参数列表</span>
        <el-button v-if="editable"
                   type="primary"
                   link
                   size="small"
                   @click="handleAddParam">
          <el-icon>
            <Plus />
          </el-icon>新增
        </el-button>
      </div>
      <div class="params-list">
        <div v-for="param in paramList"
             :key="param.id"
             class="param-item">
          <div class="param-info">
            <span class="param-code">{{ param.paramName }}</span>
            <span class="param-value">
              æ ‡å‡†å€¼ï¼š{{ param.standardValue || "-" }} {{ param.unit }}
            </span>
          </div>
          <div class="param-actions">
            <el-button v-if="editable"
                       link
                       type="primary"
                       size="small"
                       @click="handleEditParam(param)">
              ç¼–辑
            </el-button>
            <el-button v-if="editable"
                       link
                       type="danger"
                       size="small"
                       @click="handleDeleteParam(param)">
              åˆ é™¤
            </el-button>
          </div>
        </div>
        <el-empty v-if="!paramList || paramList.length === 0"
                  description="暂无参数"
                  :image-size="50" />
      </div>
    </div>
    <!-- é€‰æ‹©å‚数对话框 -->
    <el-dialog v-model="selectParamDialogVisible"
               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="getBaseParamListData">
              <template #prefix>
                <el-icon>
                  <Search />
                </el-icon>
              </template>
            </el-input>
          </div>
          <el-table :data="filteredParamList"
                    height="400"
                    border
                    highlight-current-row
                    @current-change="handleSelectParam">
            <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 class="pagination-container"
               style="margin-top: 10px;">
            <el-pagination v-model:current-page="paramPage.current"
                           v-model:page-size="paramPage.size"
                           :page-sizes="[10, 20, 50, 100]"
                           layout="total, sizes, prev, pager, next, jumper"
                           :total="paramPage.total"
                           @size-change="getBaseParamListData"
                           @current-change="getBaseParamListData"
                           size="small" />
          </div>
        </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="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.paramType == '1'">
              <el-input v-model="selectedParam.standardValue"
                        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 :active-value="true"
                         :inactive-value="false"
                         v-model="selectedParam.isRequired" />
            </el-form-item>
          </el-form>
          <el-empty v-else
                    description="请从左侧选择参数"
                    :image-size="100" />
        </div>
      </div>
      <template #footer>
        <el-button @click="selectParamDialogVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="handleParamSelectSubmit">确定</el-button>
      </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="getParamTypeTag(editParamForm.paramType)">
            {{ getParamTypeText(editParamForm.paramType) }}
          </el-tag>
        </el-form-item>
        <el-form-item label="参数格式">
          <span class="detail-text">{{ editParamForm.paramFormat || '-' }}</span>
        </el-form-item>
        <el-form-item label="单位">
          <span class="detail-text">{{ editParamForm.unit || '-' }}</span>
        </el-form-item>
        <el-form-item label="标准值"
                      v-if="editParamForm.paramType == '1'"
                      prop="standardValue">
          <el-input v-model="editParamForm.standardValue"
                    type="number"
                    placeholder="请输入标准值" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="editParamDialogVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="handleEditParamSubmit">确定</el-button>
      </template>
    </el-dialog>
  </el-dialog>
</template>
<script setup>
  import { ref, computed, watch } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Search } from "@element-plus/icons-vue";
  import {
    delProcessRouteItemParam,
    editProcessRouteItemParam,
    addProcessRouteItemParam,
  } from "@/api/productionManagement/processRouteItem.js";
  import {
    addProcessRouteItemParamOrder,
    delProcessRouteItemParamOrder,
    editProcessRouteItemParamOrder,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: "参数列表",
    },
    routeId: {
      type: Number,
      default: 0,
    },
    process: {
      type: Object,
      default: () => ({}),
    },
    paramList: {
      type: Array,
      default: () => [],
    },
    editable: {
      type: Boolean,
      default: true,
    },
    orderId: {
      type: Number,
      default: 0,
    },
    pageType: {
      type: String,
      default: "route",
    },
  });
  const emit = defineEmits(["update:modelValue", "refresh"]);
  const visible = computed({
    get: () => props.modelValue,
    set: value => emit("update:modelValue", value),
  });
  // å“åº”式数据
  const selectParamDialogVisible = ref(false);
  const editParamDialogVisible = ref(false);
  const paramSearchKeyword = ref("");
  const selectedParam = ref(null);
  const filteredParamList = ref([]);
  const paramPage = ref({
    current: 1,
    size: 10,
    total: 0,
  });
  const editParamForm = ref({
    id: null,
    processId: null,
    paramId: null,
    paramName: "",
    standardValue: null,
    sort: 1,
    isRequired: false,
    paramType: null,
    paramFormat: "",
    unit: "",
  });
  const editParamRules = ref({
    standardValue: [{ required: true, message: "请输入标准值", trigger: "blur" }],
  });
  const editParamFormRef = ref(null);
  // æ–°å¢žå‚æ•°
  const handleAddParam = () => {
    selectedParam.value = null;
    paramSearchKeyword.value = "";
    paramPage.current = 1;
    // èŽ·å–å¯é€‰å‚æ•°åˆ—è¡¨
    getBaseParamListData();
    selectParamDialogVisible.value = true;
  };
  // ç¼–辑参数
  const handleEditParam = param => {
    editParamForm.value = {
      id: param.id,
      processId: props.process.id,
      paramId: param.paramId,
      paramName: param.parameterName || param.paramName,
      standardValue: param.standardValue,
      sort: param.sort || 1,
      isRequired: param.isRequired || false,
      paramType: param.parameterType || param.paramType,
      paramFormat: param.parameterFormat || param.paramFormat,
      unit: param.unit || param.unit,
    };
    editParamDialogVisible.value = true;
  };
  // åˆ é™¤å‚æ•°
  const handleDeleteParam = param => {
    ElMessageBox.confirm("确定要删除该参数吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        // è°ƒç”¨API删除参数
        if (props.pageType === "order") {
          delProcessRouteItemParamOrder(param.id)
            .then(res => {
              ElMessage.success("删除成功");
              emit("refresh");
            })
            .catch(err => {
              ElMessage.error("删除参数失败");
              console.error("删除参数失败:", err);
            });
        } else {
          delProcessRouteItemParam(param.id)
            .then(res => {
              ElMessage.success("删除成功");
              emit("refresh");
            })
            .catch(err => {
              ElMessage.error("删除参数失败");
              console.error("删除参数失败:", err);
            });
        }
      })
      .catch(() => {});
  };
  // èŽ·å–å¯é€‰å‚æ•°åˆ—è¡¨
  const getBaseParamListData = () => {
    getBaseParamList({
      paramName: paramSearchKeyword.value,
      current: paramPage.current,
      size: paramPage.size,
    }).then(res => {
      if (res.code === 200) {
        filteredParamList.value = res.data?.records || [];
        paramPage.total = res.data?.total || 0;
      } else {
        ElMessage.error(res.msg || "查询失败");
      }
    });
  };
  // é€‰æ‹©å‚æ•°
  const handleSelectParam = param => {
    selectedParam.value = param;
  };
  // æäº¤é€‰æ‹©å‚æ•°
  const handleParamSelectSubmit = () => {
    if (!selectedParam.value) {
      ElMessage.warning("请先选择一个参数");
      return;
    }
    if (!props.process || !props.process.id) {
      ElMessage.error("工艺路线项目信息不完整");
      return;
    }
    // åˆ¤æ–­å‚数类型,只有数值类型才传标准值、最大值和最小值
    const isNumericMode = selectedParam.value.paramType == 1;
    console.log(isNumericMode, "isNumericMode");
    // è°ƒç”¨API新增参数
    if (props.pageType === "order") {
      addProcessRouteItemParamOrder({
        orderId: Number(props.orderId),
        // processId: props.process.id,
        routeItemId: props.process.id,
        // routeItemId: Number(props.routeId),
        paramId: selectedParam.value.id,
        standardValue: isNumericMode
          ? selectedParam.value.standardValue || ""
          : "",
        isRequired: selectedParam.value.isRequired || false,
        sort: selectedParam.value.sort || 1,
      })
        .then(res => {
          if (res.code === 200) {
            ElMessage.success("添加参数成功");
            selectParamDialogVisible.value = false;
            emit("refresh");
          } else {
            ElMessage.error(res.msg || "添加参数失败");
          }
        })
        .catch(err => {
          ElMessage.error("添加参数失败");
          console.error("添加参数失败:", err);
        });
    } else {
      addProcessRouteItemParam({
        routeItemId: props.process.id,
        paramId: selectedParam.value.id,
        standardValue: isNumericMode
          ? selectedParam.value.standardValue || ""
          : "",
        isRequired: selectedParam.value.isRequired || false,
        sort: selectedParam.value.sort || 1,
      })
        .then(res => {
          if (res.code === 200) {
            ElMessage.success("添加参数成功");
            selectParamDialogVisible.value = false;
            emit("refresh");
          } else {
            ElMessage.error(res.msg || "添加参数失败");
          }
        })
        .catch(err => {
          ElMessage.error("添加参数失败");
          console.error("添加参数失败:", err);
        });
    }
  };
  // æäº¤ç¼–辑参数
  const handleEditParamSubmit = () => {
    if (!editParamFormRef.value) return;
    editParamFormRef.value.validate(valid => {
      if (valid) {
        // åˆ¤æ–­å‚数类型,只有数值类型才传标准值、最大值和最小值
        const isNumericMode = editParamForm.value.paramType == 1;
        console.log(isNumericMode, "isNumericMode");
        if (props.pageType === "order") {
          editProcessRouteItemParamOrder({
            id: editParamForm.value.id,
            // routeItemId: props.process.id,
            // paramId: editParamForm.value.paramId,
            standardValue: isNumericMode
              ? editParamForm.value.standardValue || ""
              : "",
            isRequired: editParamForm.value.isRequired || false,
          })
            .then(res => {
              if (res.code === 200) {
                ElMessage.success("编辑成功");
                editParamDialogVisible.value = false;
                emit("refresh");
              } else {
                ElMessage.error(res.msg || "编辑失败");
              }
            })
            .catch(err => {
              ElMessage.error("编辑参数失败");
              console.error("编辑参数失败:", err);
            });
        } else {
          // è°ƒç”¨API修改参数
          editProcessRouteItemParam({
            id: editParamForm.value.id,
            routeItemId: props.process.id,
            paramId: editParamForm.value.paramId,
            standardValue: isNumericMode
              ? editParamForm.value.standardValue || ""
              : "",
            isRequired: editParamForm.value.isRequired || false,
          })
            .then(res => {
              if (res.code === 200) {
                ElMessage.success("编辑成功");
                editParamDialogVisible.value = false;
                emit("refresh");
              } else {
                ElMessage.error(res.msg || "编辑失败");
              }
            })
            .catch(err => {
              ElMessage.error("编辑参数失败");
              console.error("编辑参数失败:", err);
            });
        }
      }
    });
  };
  // èŽ·å–å‚æ•°ç±»åž‹æ ‡ç­¾
  const getParamTypeTag = type => {
    const typeMap = {
      1: "primary",
      2: "info",
      3: "warning",
      4: "success",
    };
    return typeMap[type] || "default";
  };
  // èŽ·å–å‚æ•°ç±»åž‹æ–‡æœ¬
  const getParamTypeText = type => {
    const typeMap = {
      1: "数值格式",
      2: "文本格式",
      3: "下拉选项",
      4: "时间格式",
    };
    return typeMap[type] || type;
  };
  watch(
    () => props.modelValue,
    newVal => {
      if (!newVal) {
        // å¼¹çª—关闭时重置数据
        selectParamDialogVisible.value = false;
        editParamDialogVisible.value = false;
        selectedParam.value = null;
        paramSearchKeyword.value = "";
        paramPage.current = 1;
        filteredParamList.value = [];
        editParamForm.value = {
          id: null,
          processId: null,
          paramId: null,
          paramName: "",
          standardValue: null,
          sort: 1,
          isRequired: false,
          paramType: null,
          paramFormat: "",
          unit: "",
        };
      }
    }
  );
</script>
<style scoped>
  .param-list-container {
    padding: 10px 0;
  }
  .params-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
    padding-bottom: 10px;
    border-bottom: 1px solid #e4e7ed;
  }
  .params-header span {
    font-size: 16px;
    font-weight: 500;
    color: #303133;
  }
  .params-list {
    max-height: 400px;
    overflow-y: auto;
  }
  .param-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    margin-bottom: 8px;
    background-color: #f9f9f9;
    border-radius: 4px;
    transition: all 0.3s ease;
  }
  .param-item:hover {
    background-color: #ecf5ff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .param-info {
    display: flex;
    align-items: center;
    gap: 20px;
    flex: 1;
  }
  .param-code {
    font-weight: 500;
    color: #303133;
    min-width: 120px;
  }
  .param-value {
    color: #606266;
    font-size: 14px;
  }
  .param-actions {
    display: flex;
    gap: 10px;
  }
  /* æ»šåŠ¨æ¡æ ·å¼ */
  .params-list::-webkit-scrollbar {
    width: 6px;
  }
  .params-list::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 3px;
  }
  .params-list::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3px;
  }
  .params-list::-webkit-scrollbar-thumb:hover {
    background: #a8a8a8;
  }
  /* é€‰æ‹©å‚数对话框样式 */
  .param-select-container {
    display: flex;
    gap: 20px;
  }
  .param-list-area {
    flex: 1;
    min-width: 400px;
  }
  .param-detail-area {
    flex: 1;
    min-width: 300px;
  }
  .area-title {
    font-size: 14px;
    font-weight: 500;
    margin-bottom: 10px;
    color: #303133;
  }
  .search-box {
    display: flex;
    gap: 10px;
    margin-bottom: 10px;
  }
  .param-detail-form {
    background: #f9f9f9;
    padding: 15px;
    border-radius: 4px;
  }
  .detail-text {
    font-weight: 500;
  }
</style>
src/views/basicData/parameterMaintenance/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,790 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">参数名称:</span>
        <el-input v-model="searchForm.paramName"
                  style="width: 200px"
                  placeholder="请输入参数名称"
                  clearable />
        <!-- å…³è”产品类型搜索 -->
        <!-- <span class="search_title ml10">关联产品类型:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 200px"d
                  placeholder="请输入关联产品类型"
                  clearable /> -->
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">搜索</el-button>
        <el-button @click="handleReset">重置</el-button>
        <el-button type="primary"
                   @click="handleAdd"
                   style="margin-left: 10px">新增参数</el-button>
        <!-- äº§å“ç±»åž‹ç»´æŠ¤æŒ‰é’® -->
        <!-- <el-button type="primary"
                   @click="handleProductTypeMaintenance"
                   style="margin-left: 10px">产品类型维护</el-button> -->
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="paramName"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                height="calc(100vh - 320px)"
                :tableLoading="tableLoading"
                :isSelection="false"
                :isShowPagination="true"
                @pagination="pagination">
      </PIMTable>
    </div>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible"
               :title="dialogTitle"
               width="500px">
      <el-form :model="formData"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="参数编码"
                      prop="paramCode">
          <el-input v-model="formData.paramCode"
                    disabled
                    placeholder="自动生成" />
        </el-form-item>
        <el-form-item label="参数名称"
                      prop="paramName">
          <el-input v-model="formData.paramName"
                    placeholder="请输入参数名称" />
        </el-form-item>
        <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="unit">
          <el-input v-model="formData.unit"
                    placeholder="请输入单位" />
        </el-form-item>
        <el-form-item label="取值格式"
                      v-if="formData.paramType == 1 || formData.paramType == 2"
                      prop="paramFormat">
          <el-input v-model="formData.paramFormat"
                    placeholder="请输入取值格式" />
          <!-- <el-select v-model="formData.paramFormat"
                     placeholder="请选择取值模式">
            <el-option label="#.00000"
                       value="#.00000" />
            <el-option label="#.0000"
                       value="#.0000" />
            <el-option label="#.000"
                       value="#.000" />
            <el-option label="#.00"
                       value="#.00" />
          </el-select> -->
        </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 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 label="是否必填"
                      prop="isRequired">
          <el-switch v-model="formData.isRequired"
                     :active-value="1"
                     :inactive-value="0" />
        </el-form-item>
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formData.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- äº§å“ç±»åž‹ç»´æŠ¤å¯¹è¯æ¡† -->
    <!-- <el-dialog v-model="productTypeDialogVisible"
               title="产品类型维护"
               width="600px">
      <div class="product-type-header">
        <el-button type="primary"
                   @click="handleAddProductType">新增产品类型</el-button>
      </div>
      <el-table :data="productTypeList"
                border
                style="width: 100%; margin-top: 10px; margin-bottom: 20px">
        <el-table-column prop="typeCode"
                         label="类型编码"
                         width="150" />
        <el-table-column prop="typeName"
                         label="类型名称" />
        <el-table-column label="操作"
                         width="150">
          <template #default="scope">
            <el-button link
                       type="primary"
                       @click="handleEditProductType(scope.row)">编辑</el-button>
            <el-button link
                       type="danger"
                       @click="handleDeleteProductType(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog> -->
    <!-- æ–°å¢ž/编辑产品类型对话框 -->
    <!-- <el-dialog v-model="productTypeFormVisible"
               :title="productTypeDialogTitle"
               width="400px">
      <el-form :model="productTypeForm"
               :rules="productTypeRules"
               ref="productTypeFormRef"
               label-width="100px">
        <el-form-item label="类型编码"
                      prop="typeCode">
          <el-input v-model="productTypeForm.typeCode"
                    placeholder="请输入类型编码" />
        </el-form-item>
        <el-form-item label="类型名称"
                      prop="typeName">
          <el-input v-model="productTypeForm.typeName"
                    placeholder="请输入类型名称" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="productTypeFormVisible = false">取消</el-button>
          <el-button type="primary"
                     @click="handleProductTypeSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog> -->
  </div>
</template>
<script setup>
  import { onMounted, ref, reactive } from "vue";
  import {
    parameterListPage,
    addParameter,
    updateParameter,
    delParameter,
    addBaseParam,
    editBaseParam,
    getBaseParamList,
    removeBaseParam,
    // getProductTypes as getProductTypesApi,
  } from "@/api/basicData/parameterMaintenance.js";
  import { listType } from "@/api/system/dict/type";
  import { deptTreeSelect } from "@/api/system/user.js";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  const tableColumn = ref([
    {
      label: "参数编码",
      prop: "paramCode",
    },
    {
      label: "参数名称",
      prop: "paramName",
    },
    {
      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: "valueMode",
    //   dataType: "tag",
    //   formatType: params => {
    //     return params === 2 ? "warning" : "success";
    //   },
    //   formatData: val => {
    //     return val === 2 ? "区间" : "单值";
    //   },
    // },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "取值格式",
      prop: "paramFormat",
    },
    {
      label: "是否必填",
      prop: "isRequired",
      dataType: "tag",
      formatType: val => {
        return val === 1 ? "success" : "info";
      },
      formatData: val => {
        return val === 1 ? "是" : "否";
      },
    },
    {
      label: "备注",
      prop: "remark",
    },
    {
      label: "创建时间",
      prop: "createTime",
    },
    {
      label: "操作",
      dataType: "action",
      width: "150",
      operation: [
        {
          name: "编辑",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "删除",
          clickFun: row => {
            handleDelete(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // æœç´¢è¡¨å•
  const searchForm = reactive({
    paramName: "",
    productName: "",
  });
  // å¯¹è¯æ¡†ç›¸å…³
  const dialogVisible = ref(false);
  const dialogTitle = ref("");
  const formRef = ref(null);
  const formData = reactive({
    id: null,
    paramCode: "",
    paramName: "",
    paramType: "",
    // valueMode: "1",
    unit: "",
    remark: "",
    isRequired: 0,
    paramFormat: "",
  });
  const rules = reactive({
    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();
          }
        },
      },
    ],
  });
  // 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 handleParamTypeChange = () => {
    if (formData.paramType === 1) {
      formData.paramFormat = "#.00000";
    } else if (formData.paramType === 4) {
      formData.paramFormat = "YYYY-MM-DD HH:mm:ss";
    } else {
      formData.paramFormat = "";
    }
    // è§¦å‘单位字段验证
    if (formRef.value) {
      formRef.value.validateField("unit");
    }
  };
  // äº§å“ç±»åž‹ç»´æŠ¤æŒ‰é’®ç‚¹å‡»äº‹ä»¶ - å·²æ³¨é‡Š
  // 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 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 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 handleQuery = () => {
    page.current = 1;
    getList();
  };
  /** é‡ç½®æŒ‰é’®æ“ä½œ */
  const handleReset = () => {
    searchForm.paramName = "";
    searchForm.productName = "";
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    // è°ƒç”¨æ–°æŽ¥å£ /baseParam/list
    getBaseParamList({
      paramName: searchForm.paramName,
      current: page.current,
      size: page.size,
    })
      .then(res => {
        tableLoading.value = false;
        if (res.code === 200) {
          tableData.value = res.data.records || [];
          page.total = res.data.total || 0;
          console.log(tableData.value, "tableData.value");
        } else {
          ElMessage.error(res.msg || "查询失败");
        }
      })
      .catch(() => {
        tableLoading.value = false;
        ElMessage.error("查询失败");
      });
  };
  // èŽ·å–äº§å“ç±»åž‹åˆ—è¡¨ - å·²æ³¨é‡Š
  // 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.id = null;
    formData.paramCode = "";
    formData.paramName = "";
    formData.paramType = "";
    // formData.valueMode = "1";
    formData.unit = "";
    formData.remark = "";
    formData.isRequired = 0;
    dialogVisible.value = true;
  };
  // ç¼–辑按钮点击事件
  const handleEdit = row => {
    isEdit.value = true;
    dialogTitle.value = "编辑参数";
    // å¡«å……表单数据
    formData.id = row.id;
    formData.paramCode = row.paramCode || "";
    formData.paramName = row.paramName || "";
    formData.paramType = row.paramType !== undefined ? row.paramType : null;
    // formData.valueMode =
    //   row.valueMode !== undefined ? String(row.valueMode) : "1";
    formData.unit = row.unit || "";
    formData.remark = row.remark || "";
    formData.paramFormat = row.paramFormat || "";
    formData.isRequired = row.isRequired || 0;
    dialogVisible.value = true;
  };
  // åˆ é™¤æŒ‰é’®ç‚¹å‡»äº‹ä»¶
  const handleDelete = row => {
    ElMessageBox.confirm("确定要删除这条数据吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        // è°ƒç”¨æ–°æŽ¥å£ /baseParam/remove/{id}
        removeBaseParam([row.id])
          .then(res => {
            ElMessage.success("删除成功");
            getList();
          })
          .catch(() => {
            ElMessage.error("删除失败");
          });
      })
      .catch(() => {
        // å–消删除
      });
  };
  // æäº¤è¡¨å•
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        if (formData.id) {
          // ç¼–辑使用新接口 /technologyParam/edit
          editBaseParam(formData)
            .then(res => {
              ElMessage.success("编辑成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              // ElMessage.error("编辑失败");
            });
        } else {
          // æ–°å¢žä½¿ç”¨æ–°æŽ¥å£ /technologyParam/add
          addBaseParam(formData)
            .then(res => {
              ElMessage.success("新增成功");
              dialogVisible.value = false;
              getList();
            })
            .catch(() => {
              ElMessage.error("新增失败");
            });
        }
      } else {
        return false;
      }
    });
  };
  const dictTypes = ref([]);
  const getDictTypes = () => {
    listType({ pageNum: 1, pageSize: 1000 }).then(res => {
      dictTypes.value = res.rows || [];
    });
  };
  onMounted(() => {
    getDictTypes();
    getList();
    // getProductTypes();
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 24px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 48px);
  }
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;
    padding: 20px;
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    transition: all 0.3s ease;
    &:hover {
      box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
    }
    .search_title {
      color: #606266;
      font-size: 14px;
      font-weight: 500;
    }
    .ml10 {
      margin-left: 10px;
    }
  }
  .table_list {
    background-color: #ffffff;
    border-radius: 6px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    overflow: hidden;
    height: calc(100vh - 230px);
  }
  :deep(.el-table) {
    border: none;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
    .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;
        letter-spacing: 0.5px;
      }
    }
    .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%
          );
          transform: scale(1.002);
          box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
        }
        td {
          border-bottom: 1px solid #f0f0f0;
          padding: 14px 0;
          color: #303133;
        }
      }
      tr.current-row {
        background: linear-gradient(
          90deg,
          rgba(102, 126, 234, 0.08) 0%,
          rgba(118, 75, 162, 0.08) 100%
        );
      }
      // æ•°å€¼å­—段样式
      .quantity-cell,
      .volume-cell,
      .dimension-cell {
        font-weight: 600;
        color: #409eff;
        font-family: "Courier New", monospace;
        text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
      }
      // è§„格字段样式
      .spec-cell {
        color: #67c23a;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // ç¼–码字段样式
      .code-cell {
        color: #e6a23c;
        font-family: "Courier New", monospace;
        font-weight: 500;
        padding: 4px 8px;
        border-radius: 4px;
      }
      // æ—¥æœŸå­—段样式
      .date-cell {
        color: #909399;
        font-style: italic;
      }
    }
    .el-table__empty-block {
      padding: 60px 0;
      background-color: #fafafa;
    }
  }
  .pagination-container {
    display: flex;
    justify-content: flex-end;
    padding: 16px 20px;
    background-color: #ffffff;
    border-top: 1px solid #ebeef5;
    border-radius: 0 0 12px 12px;
  }
  :deep(.el-button) {
    transition: all 0.3s ease;
    &:hover {
      transform: translateY(-1px);
    }
  }
  @media (max-width: 768px) {
    .app-container {
      padding: 16px;
    }
    .search_form {
      flex-direction: column;
      align-items: flex-start;
      gap: 12px;
      .el-form {
        width: 100%;
        .el-form-item {
          width: 100%;
        }
      }
      .el-button {
        margin-right: 12px;
      }
    }
    :deep(.el-table) {
      th,
      td {
        padding: 10px 0;
        font-size: 12px;
      }
    }
  }
</style>
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,9 +1,10 @@
<template>
  <div class="app-container">
    <PageHeader content="工艺路线项目" />
    <!-- å·¥è‰ºè·¯çº¿ä¿¡æ¯å±•示 -->
    <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover">
    <el-card v-if="routeInfo.processRouteCode"
             class="route-info-card"
             shadow="hover">
      <div class="route-info">
        <div class="info-item">
          <div class="info-label-wrapper">
@@ -37,7 +38,8 @@
            <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width" v-if="routeInfo.description">
        <div class="info-item full-width"
             v-if="routeInfo.description">
          <div class="info-label-wrapper">
            <span class="info-label">描述</span>
          </div>
@@ -47,435 +49,469 @@
        </div>
      </div>
    </el-card>
    <!-- è¡¨æ ¼è§†å›¾ -->
    <div v-if="viewMode === 'table'" class="section-header">
    <div v-if="viewMode === 'table'"
         class="section-header">
      <div class="section-title">工艺路线项目列表</div>
      <div class="section-actions">
        <el-button
            icon="Grid"
            @click="toggleView"
            style="margin-right: 10px;"
        >
        <el-button icon="Grid"
                   @click="toggleView"
                   style="margin-right: 10px;">
          å¡ç‰‡è§†å›¾
        </el-button>
        <el-button type="primary" @click="handleAdd">新增</el-button>
        <el-button type="primary"
                   @click="handleAdd">新增</el-button>
      </div>
    </div>
    <el-table
        v-if="viewMode === 'table'"
        ref="tableRef"
        v-loading="tableLoading"
        border
        :data="tableData"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        row-key="id"
        tooltip-effect="dark"
        class="lims-table"
    >
      <el-table-column align="center" label="序号" width="60" type="index" />
      <el-table-column label="工序名称" prop="processId" width="200">
    <el-table v-if="viewMode === 'table'"
              ref="tableRef"
              v-loading="tableLoading"
              border
              :data="tableData"
              :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
              row-key="id"
              tooltip-effect="dark"
              class="lims-table">
      <el-table-column align="center"
                       label="序号"
                       width="60"
                       type="index" />
      <el-table-column label="工序名称"
                       prop="processId"
                       width="200">
        <template #default="scope">
          {{ getProcessName(scope.row.processId) || '-' }}
        </template>
      </el-table-column>
      <el-table-column label="产品名称" prop="productName" min-width="160" />
      <el-table-column label="规格名称" prop="model" min-width="140" />
      <el-table-column label="单位" prop="unit" width="100" />
      <el-table-column label="是否质检" prop="isQuality" width="100">
      <el-table-column label="参数列表"
                       min-width="160">
        <template #default="scope">
          <el-button type="primary"
                     link
                     size="small"
                     @click="handleViewParams(scope.row)">参数列表</el-button>
        </template>
      </el-table-column>
      <el-table-column label="产品名称"
                       prop="productName"
                       min-width="160" />
      <el-table-column label="规格名称"
                       prop="model"
                       min-width="140" />
      <el-table-column label="单位"
                       prop="unit"
                       width="100" />
      <el-table-column label="是否质检"
                       prop="isQuality"
                       width="100">
        <template #default="scope">
          {{scope.row.isQuality ? "是" : "否"}}
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" fixed="right" width="150">
      <el-table-column label="操作"
                       align="center"
                       fixed="right"
                       width="150">
        <template #default="scope">
          <el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">编辑</el-button>
          <el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">删除</el-button>
          <el-button type="primary"
                     link
                     size="small"
                     @click="handleEdit(scope.row)"
                     :disabled="scope.row.isComplete">编辑</el-button>
          <el-button type="danger"
                     link
                     size="small"
                     @click="handleDelete(scope.row)"
                     :disabled="scope.row.isComplete">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- å¡ç‰‡è§†å›¾ -->
    <template v-else>
      <div class="section-header">
        <div class="section-title">工艺路线项目列表</div>
        <div class="section-actions">
          <el-button
              icon="Menu"
              @click="toggleView"
              style="margin-right: 10px;"
          >
          <el-button icon="Menu"
                     @click="toggleView"
                     style="margin-right: 10px;">
            è¡¨æ ¼è§†å›¾
          </el-button>
          <el-button type="primary" @click="handleAdd">新增</el-button>
          <el-button type="primary"
                     @click="handleAdd">新增</el-button>
        </div>
      </div>
      <div v-loading="tableLoading" class="card-container">
        <div
            ref="cardsContainer"
            class="cards-wrapper"
        >
        <div
            v-for="(item, index) in tableData"
            :key="item.id || index"
            class="process-card"
            :data-index="index"
        >
          <!-- åºå·åœ†åœˆ -->
          <div class="card-header">
            <div class="card-number">{{ index + 1 }}</div>
            <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
          </div>
          <!-- äº§å“ä¿¡æ¯ -->
          <div class="card-content">
            <div v-if="item.productName" class="product-info">
              <div class="product-name">{{ item.productName }}</div>
              <div v-if="item.model" class="product-model">
                {{ item.model }}
                <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
              </div>
              <el-tag type="primary" class="product-tag" v-if="item.isQuality">质检</el-tag>
      <div v-loading="tableLoading"
           class="card-container">
        <div ref="cardsContainer"
             class="cards-wrapper">
          <div v-for="(item, index) in tableData"
               :key="item.id || index"
               class="process-card"
               :data-index="index">
            <!-- åºå·åœ†åœˆ -->
            <div class="card-header">
              <div class="card-number">{{ index + 1 }}</div>
              <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
            </div>
            <div v-else class="product-info empty">暂无产品信息</div>
          </div>
          <!-- æ“ä½œæŒ‰é’® -->
          <div class="card-footer">
            <el-button type="primary" link size="small" @click="handleEdit(item)" :disabled="item.isComplete">编辑</el-button>
            <el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">删除</el-button>
            <!-- äº§å“ä¿¡æ¯ -->
            <div class="card-content">
              <div v-if="item.productName"
                   class="product-info">
                <div class="product-name">{{ item.productName }}</div>
                <div v-if="item.model"
                     class="product-model">
                  {{ item.model }}
                  <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
                </div>
                <el-tag type="primary"
                        class="product-tag"
                        v-if="item.isQuality">质检</el-tag>
              </div>
              <div v-else
                   class="product-info empty">暂无产品信息</div>
            </div>
            <!-- æ“ä½œæŒ‰é’® -->
            <div class="card-footer">
              <el-button type="primary"
                         link
                         size="small"
                         @click="handleEdit(item)"
                         :disabled="item.isComplete">编辑</el-button>
              <el-button type="info"
                         link
                         size="small"
                         @click="handleViewParams(item)">参数列表</el-button>
              <el-button type="danger"
                         link
                         size="small"
                         @click="handleDelete(item)"
                         :disabled="item.isComplete">删除</el-button>
            </div>
          </div>
        </div>
      </div>
      </div>
    </template>
    <div class="section-BOM">
      <div class="section-header">
        <div class="section-title">BOM</div>
        <div class="section-actions">
          <el-button type="primary"
                     @click="toggleBomEdit">
            {{ bomDataValue.isEdit ? '取消' : '编辑' }}
          </el-button>
          <el-button v-if=" bomDataValue.isEdit"
                     type="success"
                     @click="saveBomChanges">保存</el-button>
        </div>
      </div>
      <div>
        <!-- BOM表格 -->
        <el-table :data="bomTableData"
                  border
                  :preserve-expanded-content="false"
                  :default-expand-all="true"
                  style="width: 100%">
          <el-table-column type="expand">
            <template #default="props">
              <el-form ref="bomFormRef"
                       :model="bomDataValue">
                <el-table :data="props.row.bomList"
                          row-key="tempId"
                          default-expand-all
                          :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
                          style="width: 100%">
                  <el-table-column prop="productName"
                                   label="产品" />
                  <el-table-column prop="model"
                                   label="规格">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                    style="margin: 0">
                        <el-select v-model="row.model"
                                   placeholder="请选择规格"
                                   clearable
                                   :disabled="!bomDataValue.isEdit"
                                   style="width: 100%"
                                   @visible-change="(v) => { if (v) openBomProductDialog(row.tempId) }">
                          <el-option v-if="row.model"
                                     :label="row.model"
                                     :value="row.model" />
                        </el-select>
                      </el-form-item>
                      <span v-else>{{ row.model }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column prop="processName"
                                   label="消耗工序">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                    style="margin: 0">
                        <el-select v-model="row.processId"
                                   placeholder="请选择"
                                   filterable
                                   clearable
                                   :disabled="!bomDataValue.isEdit"
                                   style="width: 100%">
                          <el-option v-for="process in processOptions"
                                     :key="process.id"
                                     :label="process.name"
                                     :value="process.id" />
                        </el-select>
                      </el-form-item>
                      <span v-else>{{ row.processName }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column prop="unitQuantity"
                                   label="单位产出所需数量">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]"
                                    style="margin: 0">
                        <el-input-number v-model="row.unitQuantity"
                                         :min="0"
                                         :precision="2"
                                         :step="1"
                                         controls-position="right"
                                         style="width: 100%"
                                         :disabled="!bomDataValue.isEdit" />
                      </el-form-item>
                      <span v-else>{{ row.unitQuantity }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column v-if="pageType === 'order'"
                                   prop="demandedQuantity"
                                   label="需求总量">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]"
                                    style="margin: 0">
                        <el-input-number v-model="row.demandedQuantity"
                                         :min="0"
                                         :precision="2"
                                         :step="1"
                                         controls-position="right"
                                         style="width: 100%"
                                         :disabled="!bomDataValue.isEdit" />
                      </el-form-item>
                      <span v-else>{{ row.demandedQuantity }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column prop="unit"
                                   label="单位">
                    <template #default="{ row }">
                      <el-form-item v-if="bomDataValue.isEdit"
                                    :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]"
                                    style="margin: 0">
                        <el-input v-model="row.unit"
                                  placeholder="请输入单位"
                                  clearable
                                  :disabled="!bomDataValue.isEdit" />
                      </el-form-item>
                      <span v-else>{{ row.unit }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="操作"
                                   fixed="right"
                                   width="180">
                    <template #default="{ row }">
                      <el-button v-if="bomDataValue.isEdit"
                                 type="danger"
                                 text
                                 size="small"
                                 @click="removeBomItem(row.tempId)">删除</el-button>
                      <el-button v-if="bomDataValue.isEdit"
                                 type="primary"
                                 text
                                 size="small"
                                 @click="addBomItem2(row.tempId)">添加子项</el-button>
                    </template>
                  </el-table-column>
                </el-table>
              </el-form>
            </template>
          </el-table-column>
          <el-table-column label="BOM编号"
                           prop="bomNo" />
          <el-table-column label="产品名称"
                           prop="productName" />
          <el-table-column label="规格型号"
                           prop="model" />
        </el-table>
        <!-- <div v-if="bomDataValue.isEdit"
             style="text-align: center;border: 1px solid #e4e7ed;padding: 10px;transition: all 0.3s ease;cursor: pointer;"
             :class="{'hover-effect': bomDataValue.isEdit}">
          <el-button type="primary"
                     text
                     @click="addBomItem">
            <el-icon style="vertical-align: middle;margin-right: 5px;">
              <Plus />
            </el-icon>
            æ·»åŠ 
          </el-button>
        </div> -->
      </div>
    </div>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog
        v-model="dialogVisible"
        :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
        width="500px"
        @close="closeDialog"
    >
      <el-form
          ref="formRef"
          :model="form"
          :rules="rules"
          label-width="120px"
      >
        <el-form-item label="工序" prop="processId">
          <el-select
              v-model="form.processId"
              placeholder="请选择工序"
              clearable
              style="width: 100%"
          >
            <el-option
                v-for="process in processOptions"
                :key="process.id"
                :label="process.name"
                :value="process.id"
            />
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
               width="500px"
               @close="closeDialog">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="工序"
                      prop="processId">
          <el-select v-model="form.processId"
                     placeholder="请选择工序"
                     clearable
                     style="width: 100%">
            <el-option v-for="process in processOptions"
                       :key="process.id"
                       :label="process.name"
                       :value="process.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName && form.model 
              ? `${form.productName} - ${form.model}` 
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input
              v-model="form.unit"
              :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'"
              clearable
              :disabled="true"
          />
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="form.unit"
                    :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'"
                    clearable
                    :disabled="true" />
        </el-form-item>
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/>
        <el-form-item label="是否质检"
                      prop="isQuality">
          <el-switch v-model="form.isQuality"
                     :active-value="true"
                     inactive-value="false" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
        <el-button type="primary"
                   @click="handleSubmit"
                   :loading="submitLoading">确定</el-button>
        <el-button @click="closeDialog">取消</el-button>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <ProductSelectDialog
        v-model="showProductSelectDialog"
        @confirm="handleProductSelect"
        single
    />
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- BOM产品选择对话框 -->
    <ProductSelectDialog v-model="bomDataValue.showProductDialog"
                         @confirm="handleBomProductSelect"
                         single />
    <!-- å‚数列表对话框 -->
    <!-- :editable="!routeInfo.status" -->
    <ProcessParamListDialog v-model="showParamListDialog"
                            :title="`${currentProcess ? (currentProcess.processName || getProcessName(currentProcess.processId)) : ''} - å‚数列表`"
                            :route-id="routeId"
                            :order-id="orderId"
                            :process="currentProcess"
                            :page-type="pageType"
                            :param-list="paramList"
                            @refresh="refreshParamList" />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import { useRoute } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import Sortable from 'sortablejs'
  import {
    ref,
    computed,
    getCurrentInstance,
    onMounted,
    onUnmounted,
    nextTick,
  } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import ProcessParamListDialog from "@/components/ProcessParamListDialog.vue";
  import {
    findProcessRouteItemList,
    addOrUpdateProcessRouteItem,
    sortProcessRouteItem,
    batchDeleteProcessRouteItem,
    getProcessParamList,
  } from "@/api/productionManagement/processRouteItem.js";
  import {
    findProductProcessRouteItemList,
    deleteRouteItem,
    addRouteItem,
    findProcessParamListOrder,
    addOrUpdateProductProcessRouteItem,
    sortRouteItem,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { processList } from "@/api/productionManagement/productionProcess.js";
  import {
    queryList2,
    queryList,
    add2,
  } from "@/api/productionManagement/productStructure.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
  import Sortable from "sortablejs";
const route = useRoute()
const { proxy } = getCurrentInstance() || {};
  const route = useRoute();
  const { proxy } = getCurrentInstance() || {};
const routeId = computed(() => route.query.id);
const orderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
  const routeId = computed(() => route.query.id);
  const orderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
const tableLoading = ref(false);
const tableData = ref([]);
const dialogVisible = ref(false);
const operationType = ref('add'); // add | edit
const formRef = ref(null);
const submitLoading = ref(false);
const cardsContainer = ref(null);
const tableRef = ref(null);
const viewMode = ref('table'); // table | card
const routeInfo = ref({
  processRouteCode: '',
  productName: '',
  model: '',
  bomNo: '',
  description: ''
});
const processOptions = ref([]);
const showProductSelectDialog = ref(false);
let tableSortable = null;
let cardSortable = null;
// åˆ‡æ¢è§†å›¾
const toggleView = () => {
  viewMode.value = viewMode.value === 'table' ? 'card' : 'table';
  // åˆ‡æ¢è§†å›¾åŽé‡æ–°åˆå§‹åŒ–拖拽排序
  nextTick(() => {
    initSortable();
  const tableLoading = ref(false);
  const tableData = ref([]);
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const submitLoading = ref(false);
  const cardsContainer = ref(null);
  const tableRef = ref(null);
  const viewMode = ref("card"); // table | card
  const routeInfo = ref({
    processRouteCode: "",
    productName: "",
    model: "",
    bomNo: "",
    description: "",
  });
};
const form = ref({
  id: undefined,
  routeId: routeId.value,
  processId: undefined,
  productModelId: undefined,
  productName: "",
  model: "",
  unit: "",
  isQuality: false,
});
const rules = {
  processId: [{ required: true, message: '请选择工序', trigger: 'change' }],
  productModelId: [{ required: true, message: '请选择产品', trigger: 'change' }],
};
// æ ¹æ®å·¥åºID获取工序名称
const getProcessName = (processId) => {
  if (!processId) return '';
  const process = processOptions.value.find(p => p.id === processId);
  return process ? process.name : '';
};
// èŽ·å–åˆ—è¡¨
const getList = () => {
  tableLoading.value = true;
  const listPromise =
    pageType.value === "order"
      ? findProductProcessRouteItemList({ orderId: orderId.value })
      : findProcessRouteItemList({ routeId: routeId.value });
  listPromise
    .then(res => {
      tableData.value = res.data || [];
      tableLoading.value = false;
      // åˆ—表加载完成后初始化拖拽排序
      nextTick(() => {
        initSortable();
      });
    })
    .catch(err => {
      tableLoading.value = false;
      console.error("获取列表失败:", err);
      proxy?.$modal?.msgError("获取列表失败");
    });
};
// èŽ·å–å·¥åºåˆ—è¡¨
const getProcessList = () => {
  processList({})
    .then(res => {
      processOptions.value = res.data || [];
    })
    .catch(err => {
      console.error("获取工序失败:", err);
    });
};
// èŽ·å–å·¥è‰ºè·¯çº¿è¯¦æƒ…ï¼ˆä»Žè·¯ç”±å‚æ•°èŽ·å–ï¼‰
const getRouteInfo = () => {
  routeInfo.value = {
    processRouteCode: route.query.processRouteCode || '',
    productName: route.query.productName || '',
    model: route.query.model || '',
    bomNo: route.query.bomNo || '',
    description: route.query.description || ''
  };
};
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add';
  resetForm();
  dialogVisible.value = true;
};
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  form.value = {
    id: row.id,
    routeId: routeId.value,
    processId: row.processId,
    productModelId: row.productModelId,
    productName: row.productName || "",
    model: row.model || "",
    unit: row.unit || "",
    isQuality: row.isQuality,
  };
  dialogVisible.value = true;
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该工艺路线项目?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      // ç”Ÿäº§è®¢å•下使用 productProcessRoute çš„删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口
      const deletePromise =
        pageType.value === 'order'
          ? deleteRouteItem(row.id)
          : batchDeleteProcessRouteItem([row.id]);
      deletePromise
        .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.model = product.model;
    form.value.unit = product.unit || "";
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证
    formRef.value?.validateField('productModelId');
  }
};
// æäº¤
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      submitLoading.value = true;
      if (operationType.value === 'add') {
        // æ–°å¢žï¼šä¼ å•个对象,包含dragSort字段
        // dragSort = å½“前列表长度 + 1,表示新增记录排在最后
        const dragSort = tableData.value.length + 1;
        const isOrderPage = pageType.value === 'order';
        const addPromise = isOrderPage
          ? addRouteItem({
              productOrderId: orderId.value,
              productRouteId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
              dragSort,
            })
          : addOrUpdateProcessRouteItem({
              routeId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
              dragSort,
            });
        addPromise
          .then(() => {
            proxy?.$modal?.msgSuccess('新增成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy?.$modal?.msgError('新增失败');
          })
          .finally(() => {
            submitLoading.value = false;
          });
      } else {
        // ç¼–辑:生产订单下使用 productProcessRoute/updateRouteItem,其它情况使用工艺路线项目更新接口
        const isOrderPage = pageType.value === 'order';
        const updatePromise = isOrderPage
          ? addOrUpdateProductProcessRouteItem({
              id: form.value.id,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
            })
          : addOrUpdateProcessRouteItem({
              routeId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              id: form.value.id,
              isQuality: form.value.isQuality,
            });
        updatePromise
          .then(() => {
            proxy?.$modal?.msgSuccess('修改成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy?.$modal?.msgError('修改失败');
          })
          .finally(() => {
            submitLoading.value = false;
          });
      }
    }
  const processOptions = ref([]);
  const showProductSelectDialog = ref(false);
  const showParamListDialog = ref(false);
  const currentProcess = ref(null);
  const paramList = ref([]);
  const bomTableData = ref([]);
  const bomFormRef = ref(null);
  const bomDataValue = ref({
    dataList: [],
    showProductDialog: false,
    currentRowName: null,
    loading: false,
    isEdit: false,
  });
};
  let tableSortable = null;
  let cardSortable = null;
// é‡ç½®è¡¨å•
const resetForm = () => {
  form.value = {
  // åˆ‡æ¢è§†å›¾
  const toggleView = () => {
    viewMode.value = viewMode.value === "table" ? "card" : "table";
    // åˆ‡æ¢è§†å›¾åŽé‡æ–°åˆå§‹åŒ–拖拽排序
    nextTick(() => {
      initSortable();
    });
  };
  const form = ref({
    id: undefined,
    routeId: routeId.value,
    processId: undefined,
@@ -483,414 +519,967 @@
    productName: "",
    model: "",
    unit: "",
    isQuality: false,
  });
  const rules = {
    processId: [{ required: true, message: "请选择工序", trigger: "change" }],
    productModelId: [
      { required: true, message: "请选择产品", trigger: "change" },
    ],
  };
  formRef.value?.resetFields();
};
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogVisible.value = false;
  resetForm();
};
  // æ ¹æ®å·¥åºID获取工序名称
  const getProcessName = processId => {
    if (!processId) return "";
    const process = processOptions.value.find(p => p.id === processId);
    return process ? process.name : "";
  };
// åˆå§‹åŒ–拖拽排序
const initSortable = () => {
  destroySortable();
  if (viewMode.value === 'table') {
    // è¡¨æ ¼è§†å›¾çš„æ‹–拽排序
    if (!tableRef.value) return;
    const tbody = tableRef.value.$el.querySelector('.el-table__body tbody') ||
        tableRef.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
    if (!tbody) return;
  // èŽ·å–åˆ—è¡¨
  const getList = () => {
    tableLoading.value = true;
    const listPromise =
      pageType.value === "order"
        ? findProductProcessRouteItemList({ orderId: orderId.value })
        : findProcessRouteItemList({ routeId: routeId.value });
    tableSortable = new Sortable(tbody, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.el-table__row',
      filter: '.el-button, .el-select',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
    listPromise
      .then(res => {
        tableData.value = res.data || [];
        tableLoading.value = false;
        // åˆ—表加载完成后初始化拖拽排序
        nextTick(() => {
          initSortable();
        });
      })
      .catch(err => {
        tableLoading.value = false;
        console.error("获取列表失败:", err);
        proxy?.$modal?.msgError("获取列表失败");
      });
  };
        // é‡æ–°æŽ’序数组
        const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
        tableData.value.splice(evt.newIndex, 0, moveItem);
        // è®¡ç®—新的序号(dragSort从1开始)
        const newIndex = evt.newIndex;
        const dragSort = newIndex + 1;
        // è°ƒç”¨æŽ’序接口
        if (moveItem.id) {
          const isOrderPage = pageType.value === 'order';
          const sortPromise = isOrderPage
            ? sortRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              })
            : sortProcessRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              });
  // èŽ·å–å·¥åºåˆ—è¡¨
  const getProcessList = () => {
    processList({})
      .then(res => {
        processOptions.value = res.data || [];
      })
      .catch(err => {
        console.error("获取工序失败:", err);
      });
  };
          sortPromise
            .then(() => {
              // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
              tableData.value.forEach((item, index) => {
                if (item.id) {
                  item.dragSort = index + 1;
  // èŽ·å–å·¥è‰ºè·¯çº¿è¯¦æƒ…ï¼ˆä»Žè·¯ç”±å‚æ•°èŽ·å–ï¼‰
  const getRouteInfo = () => {
    routeInfo.value = {
      processRouteCode: route.query.processRouteCode || "",
      productName: route.query.productName || "",
      model: route.query.model || "",
      bomNo: route.query.bomNo || "",
      description: route.query.description || "",
      status: !(route.query.status == 1 || route.query.status === "false"),
    };
    if (pageType.value === "order") {
      queryList2(route.query.orderId)
        .then(res => {
          if (res.data) {
            // ä¸ºBOM数据设置tempId
            const setTempIdRecursively = items => {
              items.forEach(item => {
                item.tempId = item.id || new Date().getTime();
                if (item.children && item.children.length > 0) {
                  setTempIdRecursively(item.children);
                }
              });
              proxy?.$modal?.msgSuccess('排序成功');
            };
            setTempIdRecursively(res.data);
            bomTableData.value = [
              {
                bomNo: routeInfo.value.bomNo,
                dictLabel: routeInfo.value.dictLabel,
                productCode: "",
                productName: routeInfo.value.productName,
                model: routeInfo.value.model,
                bomList: res.data,
              },
            ];
            // ä¿å­˜åŽŸå§‹BOM数据
            bomDataValue.value.dataList = res.data;
          }
        })
        .catch(err => {
          console.error("获取BOM数据失败:", err);
        });
    } else {
      queryList(Number(route.query.bomId))
        .then(res => {
          if (res.data) {
            // ä¸ºBOM数据设置tempId
            const setTempIdRecursively = items => {
              items.forEach(item => {
                item.tempId = item.id || new Date().getTime();
                if (item.children && item.children.length > 0) {
                  setTempIdRecursively(item.children);
                }
              });
            };
            setTempIdRecursively(res.data);
            bomTableData.value = [
              {
                bomNo: routeInfo.value.bomNo,
                dictLabel: routeInfo.value.dictLabel,
                productCode: "",
                productName: routeInfo.value.productName,
                model: routeInfo.value.model,
                bomList: res.data,
              },
            ];
            // ä¿å­˜åŽŸå§‹BOM数据
            bomDataValue.value.dataList = res.data;
          }
        })
        .catch(err => {
          console.error("获取BOM数据失败:", err);
        });
    }
  };
  // æ–°å¢ž
  const handleAdd = () => {
    operationType.value = "add";
    resetForm();
    dialogVisible.value = true;
  };
  // ç¼–辑
  const handleEdit = row => {
    operationType.value = "edit";
    form.value = {
      id: row.id,
      routeId: routeId.value,
      processId: row.processId,
      productModelId: row.productModelId,
      productName: row.productName || "",
      model: row.model || "",
      unit: row.unit || "",
      isQuality: row.isQuality,
    };
    dialogVisible.value = true;
  };
  // åˆ é™¤
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该工艺路线项目?", "提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        // ç”Ÿäº§è®¢å•下使用 productProcessRoute çš„删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口
        const deletePromise =
          pageType.value === "order"
            ? deleteRouteItem(row.id)
            : batchDeleteProcessRouteItem([row.id]);
        deletePromise
          .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.model = product.model;
      form.value.unit = product.unit || "";
      showProductSelectDialog.value = false;
      // è§¦å‘表单验证
      formRef.value?.validateField("productModelId");
    }
  };
  // æäº¤
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        submitLoading.value = true;
        if (operationType.value === "add") {
          // æ–°å¢žï¼šä¼ å•个对象,包含dragSort字段
          // dragSort = å½“前列表长度 + 1,表示新增记录排在最后
          const dragSort = tableData.value.length + 1;
          const isOrderPage = pageType.value === "order";
          const addPromise = isOrderPage
            ? addRouteItem({
                productOrderId: orderId.value,
                productRouteId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                dragSort,
              })
            : addOrUpdateProcessRouteItem({
                routeId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                dragSort,
              });
          addPromise
            .then(() => {
              proxy?.$modal?.msgSuccess("新增成功");
              closeDialog();
              getList();
            })
            .catch((err) => {
              // æŽ’序失败,恢复原数组
              tableData.value.splice(newIndex, 1);
              tableData.value.splice(evt.oldIndex, 0, moveItem);
              proxy?.$modal?.msgError('排序失败');
              console.error("排序失败:", err);
            .catch(() => {
              proxy?.$modal?.msgError("新增失败");
            })
            .finally(() => {
              submitLoading.value = false;
            });
        } else {
          // ç¼–辑:生产订单下使用 productProcessRoute/updateRouteItem,其它情况使用工艺路线项目更新接口
          const isOrderPage = pageType.value === "order";
          const updatePromise = isOrderPage
            ? addOrUpdateProductProcessRouteItem({
                id: form.value.id,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
              })
            : addOrUpdateProcessRouteItem({
                routeId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                id: form.value.id,
                isQuality: form.value.isQuality,
              });
          updatePromise
            .then(() => {
              proxy?.$modal?.msgSuccess("修改成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy?.$modal?.msgError("修改失败");
            })
            .finally(() => {
              submitLoading.value = false;
            });
        }
      }
    });
  } else {
    // å¡ç‰‡è§†å›¾çš„æ‹–拽排序
    if (!cardsContainer.value) return;
  };
    cardSortable = new Sortable(cardsContainer.value, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.process-card',
      filter: '.el-button',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    form.value = {
      id: undefined,
      routeId: routeId.value,
      processId: undefined,
      productModelId: undefined,
      productName: "",
      model: "",
      unit: "",
    };
    formRef.value?.resetFields();
  };
        // é‡æ–°æŽ’序数组
        const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
        tableData.value.splice(evt.newIndex, 0, moveItem);
        // è®¡ç®—新的序号(dragSort从1开始)
        const newIndex = evt.newIndex;
        const dragSort = newIndex + 1;
        // è°ƒç”¨æŽ’序接口
        if (moveItem.id) {
          const isOrderPage = pageType.value === 'order';
          const sortPromise = isOrderPage
            ? sortRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              })
            : sortProcessRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              });
  // å…³é—­å¼¹çª—
  const closeDialog = () => {
    dialogVisible.value = false;
    resetForm();
  };
          sortPromise
            .then(() => {
              // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
              tableData.value.forEach((item, index) => {
                if (item.id) {
                  item.dragSort = index + 1;
                }
              });
              proxy?.$modal?.msgSuccess('排序成功');
            })
            .catch((err) => {
              // æŽ’序失败,恢复原数组
              tableData.value.splice(newIndex, 1);
              tableData.value.splice(evt.oldIndex, 0, moveItem);
              proxy?.$modal?.msgError('排序失败');
              console.error("排序失败:", err);
            });
  // æŸ¥çœ‹å‚数列表
  const handleViewParams = row => {
    currentProcess.value = row;
    const query = {
      routeItemId: row.id,
      orderId: orderId.value,
    };
    const apiPromise =
      pageType.value === "order"
        ? findProcessParamListOrder(query)
        : getProcessParamList(query);
    apiPromise
      .then(res => {
        paramList.value = res.data || [];
        showParamListDialog.value = true;
      })
      .catch(err => {
        console.error("获取参数列表失败:", err);
        proxy?.$modal?.msgError("获取参数列表失败");
      });
  };
  // åˆ·æ–°å‚数列表
  const refreshParamList = () => {
    if (currentProcess.value) {
      handleViewParams(currentProcess.value);
    }
  };
  // BOM相关方法
  // åˆ‡æ¢BOM编辑模式
  const toggleBomEdit = () => {
    bomDataValue.value.isEdit = !bomDataValue.value.isEdit;
    if (!bomDataValue.value.isEdit) {
      // å–消编辑时重新加载数据
      getRouteInfo();
    }
  };
  // æ·»åŠ BOM项
  const addBomItem = () => {
    if (bomTableData.value.length > 0) {
      const newItem = {
        parentId: "",
        parentTempId: "",
        productName: "",
        productId: "",
        model: undefined,
        productModelId: undefined,
        processId: "",
        processName: "",
        unitQuantity: 0,
        demandedQuantity: 0,
        unit: "",
        children: [],
        tempId: new Date().getTime(),
      };
      bomTableData.value[0].bomList.push(newItem);
    }
  };
  // æ·»åŠ BOM子项
  const addBomItem2 = tempId => {
    const addChildItem = (items, tempId) => {
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (item.tempId === tempId) {
          if (!item.children) {
            item.children = [];
          }
          item.children.push({
            parentId: item.id || "",
            parentTempId: item.tempId || "",
            productName: "",
            productId: "",
            model: undefined,
            productModelId: undefined,
            processId: "",
            processName: "",
            unitQuantity: 0,
            demandedQuantity: 0,
            unit: "",
            children: [],
            tempId: new Date().getTime(),
          });
          return true;
        }
        if (item.children && item.children.length > 0) {
          if (addChildItem(item.children, tempId)) {
            return true;
          }
        }
      }
    });
  }
};
      return false;
    };
// é”€æ¯æ‹–拽排序
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (cardSortable) {
    cardSortable.destroy();
    cardSortable = null;
  }
};
    if (bomTableData.value.length > 0) {
      addChildItem(bomTableData.value[0].bomList, tempId);
    }
  };
onMounted(() => {
  getRouteInfo();
  getList();
  getProcessList();
});
  // åˆ é™¤BOM项
  const removeBomItem = tempId => {
    if (bomTableData.value.length > 0) {
      const removeFromList = (items, tempId) => {
        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          if (item.tempId === tempId) {
            items.splice(i, 1);
            return true;
          }
          if (item.children && item.children.length > 0) {
            if (removeFromList(item.children, tempId)) {
              return true;
            }
          }
        }
        return false;
      };
      removeFromList(bomTableData.value[0].bomList, tempId);
    }
  };
onUnmounted(() => {
  destroySortable();
});
  // æ‰“å¼€BOM产品选择对话框
  const openBomProductDialog = tempId => {
    bomDataValue.value.currentRowName = tempId;
    bomDataValue.value.showProductDialog = true;
  };
  // å¤„理BOM产品选择
  const handleBomProductSelect = products => {
    if (products && products.length > 0) {
      const product = products[0];
      const updateProductInfo = (items, tempId, productData) => {
        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          if (item.tempId === tempId) {
            item.productName = productData.productName;
            item.model = productData.model;
            item.productModelId = productData.id;
            item.unit = productData.unit || "";
            return true;
          }
          if (item.children && item.children.length > 0) {
            if (updateProductInfo(item.children, tempId, productData)) {
              return true;
            }
          }
        }
        return false;
      };
      if (bomTableData.value.length > 0) {
        updateProductInfo(
          bomTableData.value[0].bomList,
          bomDataValue.value.currentRowName,
          product
        );
      }
      bomDataValue.value.showProductDialog = false;
    }
  };
  // ä¿å­˜BOM更改
  const saveBomChanges = () => {
    const validateBomData = (items, isTopLevel = false) => {
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (!item.productModelId) {
          ElMessage.error("请选择产品");
          return false;
        }
        if (!isTopLevel && !item.processId) {
          ElMessage.error("请选择消耗工序");
          return false;
        }
        if (
          item.unitQuantity === undefined ||
          item.unitQuantity === null ||
          item.unitQuantity === 0
        ) {
          ElMessage.error("请填写单位产出所需数量");
          return false;
        }
        if (
          pageType.value === "order" &&
          (item.demandedQuantity === undefined ||
            item.demandedQuantity === null ||
            item.demandedQuantity === 0)
        ) {
          ElMessage.error("请输入需求总量");
          return false;
        }
        if (item.children && item.children.length > 0) {
          if (!validateBomData(item.children, false)) {
            return false;
          }
        }
      }
      return true;
    };
    if (bomTableData.value.length > 0) {
      if (!validateBomData(bomTableData.value[0].bomList, true)) {
        return;
      }
    }
    const processBomItem = (item, parentId = null, parentTempId = null) => {
      const cleanItem = {
        id: item.id || null,
        orderId: Number(orderId.value) || null,
        parentId: parentId,
        parentTempId: parentTempId || null,
        productModelId: item.productModelId || null,
        processId: item.processId || null,
        unitQuantity: item.unitQuantity || 0,
        demandedQuantity: item.demandedQuantity || 0,
        unit: item.unit || "",
        tempId: item.tempId || new Date().getTime(),
        bomId: Number(route.query.bomId) || null,
        children: [],
      };
      if (item.children && item.children.length > 0) {
        cleanItem.children = item.children.map(child =>
          processBomItem(child, item.id, item.tempId)
        );
      }
      return cleanItem;
    };
    const saveData = {
      orderId: Number(orderId.value),
      bomId: Number(route.query.bomId),
      children: bomTableData.value[0].bomList.map(item => processBomItem(item)),
    };
    const savePromise =
      pageType.value === "order" ? add2(saveData) : add(saveData);
    savePromise
      .then(() => {
        proxy?.$modal?.msgSuccess("保存成功");
        bomDataValue.value.isEdit = false;
        getRouteInfo();
      })
      .catch(err => {
        console.error("保存BOM失败:", err);
        proxy?.$modal?.msgError("保存失败");
      });
  };
  // åˆå§‹åŒ–拖拽排序
  const initSortable = () => {
    destroySortable();
    if (viewMode.value === "table") {
      // è¡¨æ ¼è§†å›¾çš„æ‹–拽排序
      if (!tableRef.value) return;
      const tbody =
        tableRef.value.$el.querySelector(".el-table__body tbody") ||
        tableRef.value.$el.querySelector(
          ".el-table__body-wrapper > table > tbody"
        );
      if (!tbody) return;
      tableSortable = new Sortable(tbody, {
        animation: 150,
        ghostClass: "sortable-ghost",
        handle: ".el-table__row",
        filter: ".el-button, .el-select",
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex])
            return;
          // é‡æ–°æŽ’序数组
          const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
          tableData.value.splice(evt.newIndex, 0, moveItem);
          // è®¡ç®—新的序号(dragSort从1开始)
          const newIndex = evt.newIndex;
          const dragSort = newIndex + 1;
          // è°ƒç”¨æŽ’序接口
          if (moveItem.id) {
            const isOrderPage = pageType.value === "order";
            const sortPromise = isOrderPage
              ? sortRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                })
              : sortProcessRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                });
            sortPromise
              .then(() => {
                // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
                tableData.value.forEach((item, index) => {
                  if (item.id) {
                    item.dragSort = index + 1;
                  }
                });
                proxy?.$modal?.msgSuccess("排序成功");
              })
              .catch(err => {
                // æŽ’序失败,恢复原数组
                tableData.value.splice(newIndex, 1);
                tableData.value.splice(evt.oldIndex, 0, moveItem);
                proxy?.$modal?.msgError("排序失败");
                console.error("排序失败:", err);
              });
          }
        },
      });
    } else {
      // å¡ç‰‡è§†å›¾çš„æ‹–拽排序
      if (!cardsContainer.value) return;
      cardSortable = new Sortable(cardsContainer.value, {
        animation: 150,
        ghostClass: "sortable-ghost",
        handle: ".process-card",
        filter: ".el-button",
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex])
            return;
          // é‡æ–°æŽ’序数组
          const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
          tableData.value.splice(evt.newIndex, 0, moveItem);
          // è®¡ç®—新的序号(dragSort从1开始)
          const newIndex = evt.newIndex;
          const dragSort = newIndex + 1;
          // è°ƒç”¨æŽ’序接口
          if (moveItem.id) {
            const isOrderPage = pageType.value === "order";
            const sortPromise = isOrderPage
              ? sortRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                })
              : sortProcessRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                });
            sortPromise
              .then(() => {
                // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
                tableData.value.forEach((item, index) => {
                  if (item.id) {
                    item.dragSort = index + 1;
                  }
                });
                proxy?.$modal?.msgSuccess("排序成功");
              })
              .catch(err => {
                // æŽ’序失败,恢复原数组
                tableData.value.splice(newIndex, 1);
                tableData.value.splice(evt.oldIndex, 0, moveItem);
                proxy?.$modal?.msgError("排序失败");
                console.error("排序失败:", err);
              });
          }
        },
      });
    }
  };
  // é”€æ¯æ‹–拽排序
  const destroySortable = () => {
    if (tableSortable) {
      tableSortable.destroy();
      tableSortable = null;
    }
    if (cardSortable) {
      cardSortable.destroy();
      cardSortable = null;
    }
  };
  onMounted(() => {
    getRouteInfo();
    getList();
    getProcessList();
  });
  onUnmounted(() => {
    destroySortable();
  });
</script>
<style scoped>
.card-container {
  padding: 20px 0;
}
  .card-container {
    padding: 20px 0;
  }
.cards-wrapper {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding: 10px 0;
  min-height: 200px;
}
  .cards-wrapper {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding: 10px 0;
    min-height: 200px;
  }
.cards-wrapper::-webkit-scrollbar {
  height: 8px;
}
  .cards-wrapper::-webkit-scrollbar {
    height: 8px;
  }
.cards-wrapper::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 4px;
}
  .cards-wrapper::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
  }
.cards-wrapper::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 4px;
}
  .cards-wrapper::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 4px;
  }
.cards-wrapper::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}
  .cards-wrapper::-webkit-scrollbar-thumb:hover {
    background: #a8a8a8;
  }
.process-card {
  flex-shrink: 0;
  width: 220px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 16px;
  display: flex;
  flex-direction: column;
  cursor: move;
  transition: all 0.3s;
}
  .process-card {
    flex-shrink: 0;
    width: 220px;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    padding: 16px;
    display: flex;
    flex-direction: column;
    cursor: move;
    transition: all 0.3s;
  }
.process-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  transform: translateY(-2px);
}
  .process-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
  }
.card-header {
  text-align: center;
  margin-bottom: 12px;
}
  .card-header {
    text-align: center;
    margin-bottom: 12px;
  }
.card-number {
  width: 36px;
  height: 36px;
  line-height: 36px;
  border-radius: 50%;
  background: #409eff;
  color: #fff;
  font-weight: bold;
  font-size: 16px;
  margin: 0 auto 8px;
}
  .card-number {
    width: 36px;
    height: 36px;
    line-height: 36px;
    border-radius: 50%;
    background: #409eff;
    color: #fff;
    font-weight: bold;
    font-size: 16px;
    margin: 0 auto 8px;
  }
.card-process-name {
  font-size: 14px;
  color: #333;
  font-weight: 500;
  word-break: break-all;
}
  .card-process-name {
    font-size: 14px;
    color: #333;
    font-weight: 500;
    word-break: break-all;
  }
.card-content {
  flex: 1;
  margin-bottom: 12px;
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
}
  .card-content {
    flex: 1;
    margin-bottom: 12px;
    min-height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
.product-info {
  font-size: 13px;
  color: #666;
  text-align: center;
  width: 100%;
}
  .product-info {
    font-size: 13px;
    color: #666;
    text-align: center;
    width: 100%;
  }
.product-info.empty {
  color: #999;
  text-align: center;
  padding: 20px 0;
}
  .product-info.empty {
    color: #999;
    text-align: center;
    padding: 20px 0;
  }
.product-name {
  margin-bottom: 6px;
  word-break: break-all;
  line-height: 1.5;
  text-align: center;
}
  .product-name {
    margin-bottom: 6px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
  }
.product-model {
  color: #909399;
  font-size: 12px;
  word-break: break-all;
  line-height: 1.5;
  text-align: center;
}
  .product-model {
    color: #909399;
    font-size: 12px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
  }
.product-unit {
  margin-left: 4px;
  color: #409eff;
}
  .product-unit {
    margin-left: 4px;
    color: #409eff;
  }
.product-tag {
  margin: 10px 0;
}
  .product-tag {
    margin: 10px 0;
  }
.card-footer {
  display: flex;
  justify-content: space-around;
  padding-top: 12px;
  border-top: 1px solid #f0f0f0;
}
  .card-footer {
    display: flex;
    justify-content: space-around;
    padding-top: 12px;
    border-top: 1px solid #f0f0f0;
  }
.card-footer .el-button {
  padding: 0;
  font-size: 12px;
}
  .card-footer .el-button {
    padding: 0;
    font-size: 12px;
  }
:deep(.sortable-ghost) {
  opacity: 0.5;
  background-color: #f5f7fa !important;
}
  :deep(.sortable-ghost) {
    opacity: 0.5;
    background-color: #f5f7fa !important;
  }
:deep(.sortable-drag) {
  opacity: 0.8;
}
  :deep(.sortable-drag) {
    opacity: 0.8;
  }
/* è¡¨æ ¼è§†å›¾æ ·å¼ */
:deep(.el-table__row) {
  transition: background-color 0.2s;
  cursor: move;
}
  /* è¡¨æ ¼è§†å›¾æ ·å¼ */
  :deep(.el-table__row) {
    transition: background-color 0.2s;
    cursor: move;
  }
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
  :deep(.el-table__row:hover) {
    background-color: #f9fafc !important;
  }
/* åŒºåŸŸæ ‡é¢˜æ ·å¼ */
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
  /* åŒºåŸŸæ ‡é¢˜æ ·å¼ */
  .section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
  padding-left: 12px;
  position: relative;
  margin-bottom: 0;
}
  .section-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    padding-left: 12px;
    position: relative;
    margin-bottom: 0;
  }
.section-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 16px;
  background: #409eff;
  border-radius: 2px;
}
  .section-title::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 16px;
    background: #409eff;
    border-radius: 2px;
  }
.section-actions {
  display: flex;
  align-items: center;
}
  .section-actions {
    display: flex;
    align-items: center;
  }
/* å·¥è‰ºè·¯çº¿ä¿¡æ¯å¡ç‰‡æ ·å¼ */
.route-info-card {
  margin-bottom: 20px;
  border: 1px solid #e4e7ed;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 8px;
  overflow: hidden;
}
  /* å·¥è‰ºè·¯çº¿ä¿¡æ¯å¡ç‰‡æ ·å¼ */
  .route-info-card {
    margin-bottom: 20px;
    border: 1px solid #e4e7ed;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 8px;
    overflow: hidden;
  }
.route-info {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  padding: 4px;
}
  .route-info {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    padding: 4px;
  }
.info-item {
  display: flex;
  flex-direction: column;
  background: #ffffff;
  border-radius: 6px;
  padding: 14px 16px;
  border: 1px solid #f0f2f5;
  transition: all 0.3s ease;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
  .info-item {
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 6px;
    padding: 14px 16px;
    border: 1px solid #f0f2f5;
    transition: all 0.3s ease;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  }
.info-item:hover {
  border-color: #409eff;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
  transform: translateY(-1px);
}
  .info-item:hover {
    border-color: #409eff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
    transform: translateY(-1px);
  }
.info-item.full-width {
  grid-column: 1 / -1;
}
  .info-item.full-width {
    grid-column: 1 / -1;
  }
.info-label-wrapper {
  margin-bottom: 8px;
}
  .info-label-wrapper {
    margin-bottom: 8px;
  }
.info-label {
  display: inline-block;
  color: #909399;
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 0;
  position: relative;
}
  .info-label {
    display: inline-block;
    color: #909399;
    font-size: 12px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 2px 0;
    position: relative;
  }
.info-label::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 20px;
  height: 2px;
  background: linear-gradient(90deg, #409eff, transparent);
  border-radius: 1px;
}
  .info-label::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 20px;
    height: 2px;
    background: linear-gradient(90deg, #409eff, transparent);
    border-radius: 1px;
  }
.info-value-wrapper {
  flex: 1;
}
  .info-value-wrapper {
    flex: 1;
  }
.info-value {
  display: block;
  color: #303133;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.5;
  word-break: break-all;
}
  .info-value {
    display: block;
    color: #303133;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.5;
    word-break: break-all;
  }
  .section-BOM {
    margin-top: 20px;
  }
  .hover-effect:hover {
    border-color: #409eff;
    background-color: #ecf5ff;
    transform: translateY(-2px);
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  }
</style>
src/views/productionManagement/productionProcess/Edit.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/productionProcess/New.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/productionProcess/index.vue
@@ -1,319 +1,1090 @@
<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 class="process-device">关联设备: {{ process.deviceName || '未关联' }}</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>
                <el-tag v-if="process.type !== null && process.type !== undefined"
                        size="small"
                        :type="process.type == 1 ? 'primary' : 'success'"
                        style="margin-left: 8px">
                  {{ process.type == 0 ? '计时' : '计件' }}
                </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="openParamDialog">
            <el-icon>
              <Plus />
            </el-icon>选择参数
          </el-button>
        </div>
        <div class="param-table-wrapper">
          <PIMTable v-if="selectedProcess"
                    rowKey="id"
                    :column="paramColumn"
                    :tableData="paramList"
                    :page="paramPage2"
                    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" />
        </el-form-item>
        <el-form-item label="计费类型"
                      prop="type">
          <el-radio-group v-model="processForm.type">
            <el-radio :label="0">计时</el-radio>
            <el-radio :label="1">计件</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="关联设备"
                      prop="deviceLedgerId">
          <el-select v-model="processForm.deviceLedgerId"
                     placeholder="请选择设备"
                     clearable
                     filterable
                     style="width: 100%">
            <el-option v-for="item in deviceOptions"
                       :key="item.id"
                       :label="item.deviceName"
                       :value="item.id" />
          </el-select>
        </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="300"
                    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 class="pagination-container"
               style="margin-top: 10px;">
            <el-pagination v-model:current-page="paramPage.current"
                           v-model:page-size="paramPage.size"
                           :page-sizes="[10, 20, 50, 100]"
                           layout="total, sizes, prev, pager, next, jumper"
                           :total="paramPage.total"
                           @size-change="handleParamSizeChange"
                           @current-change="handleParamCurrentChange"
                           size="small" />
          </div>
        </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="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="标准值">
              <el-input v-model="selectedParam.standardValue"
                        type="number"
                        placeholder="请输入默认值" />
            </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="标准值"
                      prop="standardValue">
          <el-input v-model="editParamForm.standardValue"
                    type="number"
                    placeholder="请输入标准值" />
        </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 { getDeviceLedger } from "@/api/equipmentManagement/ledger";
  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 deviceOptions = ref([]);
  // å‚数列表数据
  const paramList = ref([]);
  const paramLoading = ref(false);
  // æ•°æ®å­—å…¸
  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,
    deviceLedgerId: null,
    type: 0,
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
  const processRules = {
    no: [{ required: true, message: "请输入工序编码", trigger: "blur" }],
    name: [{ required: true, message: "请输入工序名称", trigger: "blur" }],
    salaryQuota: [
      {
        required: false,
        message: "请输入工资定额",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (isNaN(value) || value < 0) {
            callback(new Error("工资定额必须是非负数字"));
          } else {
            callback();
          }
        },
      },
    ],
    deviceLedgerId: [
      { required: false, message: "请选择设备", trigger: "change" },
    ],
    type: [{ required: false, message: "请选择计费类型", trigger: "change" }],
  };
  // å‚数对话框
  const paramDialogVisible = ref(false);
  const availableParamList = ref([]);
  const filteredParamList = ref([]);
  const selectedParam = ref(null);
  const paramSearchKeyword = ref("");
  // å¯é€‰å‚数分页
  const paramPage = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  // ç¼–辑参数对话框
  const editParamDialogVisible = ref(false);
  const editParamFormRef = ref(null);
  const editParamForm = reactive({
    id: null,
    processId: null,
    paramId: null,
    paramName: "",
    standardValue: null,
    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();
          }
        },
      },
    ],
  };
  // å‚数表格列配置
  const paramColumn = ref([
    {
      label: "工序编号",
      prop: "no",
      label: "参数名称",
      prop: "paramName",
    },
    {
      label: "工序名称",
      prop: "name",
    },
    {
      label: "工序类型",
      prop: "typeText",
    },
    {
      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",
    },
    {
      dataType: "action",
      label: "单位",
      prop: "unit",
    },
    {
      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({
  // èŽ·å–å·¥åºåˆ—è¡¨
  const getProcessList = () => {
    processLoading.value = true;
    getProcessListApi()
      .then(res => {
        processValueList.value = res.data.records || [];
      })
      .catch(() => {
        ElMessage.error("获取工序列表失败");
      })
      .finally(() => {
        processLoading.value = false;
      });
  };
  const loadDeviceName = async () => {
    try {
      const { data } = await getDeviceLedger();
      deviceOptions.value = data || [];
    } catch (error) {
      console.error("加载设备列表失败", error);
    }
  };
  const paramPage2 = ref({
    current: 1,
    size: 100,
    size: 10,
    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 getParamList = processId => {
    paramLoading.value = true;
    console.log(paramPage2.value, "paramPage2.value");
    getProcessParamList(processId, {
      current: paramPage2.value.current,
      size: paramPage2.value.size,
    })
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
          typeText: item.type !== undefined && item.type !== null ? (item.type === 0 ? "计时" : "计件") : "",
        }));
        page.total = res.data.total;
        paramList.value = res.data.records || [];
        paramPage2.value.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      .catch(() => {
        ElMessage.error("获取参数列表失败");
      })
      .finally(() => {
        paramLoading.value = false;
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  // é€‰æ‹©å·¥åº
  const selectProcess = process => {
    selectedProcess.value = process;
    getParamList(process.id);
  };
  // æ‰“开新增弹框
  const showNewModal = () => {
    isShowNewModal.value = true;
  // å·¥åºæ“ä½œ
  const handleAddProcess = () => {
    isProcessEdit.value = false;
    processForm.id = null;
    processForm.no = "";
    processForm.name = "";
    processForm.salaryQuota = null;
    processForm.isQuality = false;
    processForm.remark = "";
    processForm.status = true;
    processForm.deviceLedgerId = null;
    processForm.type = 0;
    loadDeviceName();
    processDialogVisible.value = true;
  };
  const showEditModal = row => {
    isShowEditModal.value = true;
    record.value = row;
  const handleEditProcess = async process => {
    isProcessEdit.value = true;
    await loadDeviceName(); // Ensure deviceOptions is loaded before setting deviceLedgerId
    processForm.id = process.id;
    processForm.no = process.no;
    processForm.name = process.name;
    processForm.salaryQuota = process.salaryQuota;
    processForm.isQuality = !!process.isQuality;
    processForm.remark = process.remark || "";
    processForm.status = process.status;
    processForm.deviceLedgerId = Number(process.deviceLedgerId);
    processForm.type = process.type;
    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 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) {
        if (processForm.id) {
          update(processForm)
            .then(() => {
              ElMessage.success("编辑成功");
              processDialogVisible.value = false;
              getProcessList();
            })
            .catch(() => {
              ElMessage.error("编辑失败");
            });
        } else {
          add(processForm)
            .then(() => {
              ElMessage.success("新增成功");
              processDialogVisible.value = false;
              getProcessList();
            })
            .catch(() => {
              ElMessage.error("新增失败");
            });
        }
      }
      getList();
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
    });
  };
  const openParamDialog = () => {
    paramSearchKeyword.value = "";
    if (!selectedProcess.value) {
      ElMessage.warning("请先选择一个工序");
      return;
    }
    // èŽ·å–å¯é€‰å‚æ•°åˆ—è¡¨
    getBaseParamList({
      paramName: paramSearchKeyword.value,
      current: paramPage.current,
      size: paramPage.size,
    }).then(res => {
      if (res.code === 200) {
        filteredParamList.value = res.data?.records || [];
        paramPage.total = res.data?.total || 0;
      } else {
        ElMessage.error(res.msg || "查询失败");
      }
    });
    console.log(filteredParamList.value, "可选参数列表");
    selectedParam.value = null;
    paramDialogVisible.value = true;
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = error => {
    proxy.$modal.msgError("导入失败:" + (error.message || "未知错误"));
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    if (importDialogRef.value) {
      importDialogRef.value.clearFiles();
  // å‚数操作
  const handleSelectParam = () => {
    if (!selectedProcess.value) {
      ElMessage.warning("请先选择一个工序");
      return;
    }
    // èŽ·å–å¯é€‰å‚æ•°åˆ—è¡¨
    getBaseParamList({
      paramName: paramSearchKeyword.value,
      current: paramPage.current,
      size: paramPage.size,
    }).then(res => {
      if (res.code === 200) {
        filteredParamList.value = res.data?.records || [];
        paramPage.total = res.data?.total || 0;
      } else {
        ElMessage.error(res.msg || "查询失败");
      }
    });
    console.log(filteredParamList.value, "可选参数列表");
    selectedParam.value = null;
    paramDialogVisible.value = true;
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = async () => {
    try {
      const res = await downloadTemplate();
      const blob = new Blob([res], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  const handleParamSelect = row => {
    selectedParam.value = row;
  };
  const handleParamSearch = () => {
    // é‡ç½®åˆ†é¡µ
    paramPage.current = 1;
    // é‡æ–°åŠ è½½æ•°æ®
    handleSelectParam();
  };
  // å¤„理分页大小变化
  const handleParamSizeChange = size => {
    paramPage.size = size;
    handleSelectParam();
  };
  // å¤„理当前页码变化
  const handleParamCurrentChange = current => {
    paramPage.current = current;
    handleSelectParam();
  };
  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 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.standardValue = row.standardValue;
    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;
    }
    addProcessParam({
      processId: selectedProcess.value.id,
      paramId: selectedParam.value.id,
      standardValue: selectedParam.value.standardValue,
      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 => {
    console.log(obj, "obj");
    paramPage2.value.current = obj.page;
    paramPage2.value.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;
        margin-bottom: 4px;
      }
      .process-device {
        font-size: 12px;
        color: #606266;
        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;
    min-width: 0;
  }
  .param-table-wrapper {
    flex: 1;
    padding: 0 20px 20px;
    overflow: auto;
    min-width: 100%;
  }
  /* è¡¨æ ¼æ¨ªå‘滚动 */
  .param-table-wrapper :deep(.el-table) {
    min-width: 100%;
  }
  .param-table-wrapper :deep(.el-table__body-wrapper) {
    overflow-x: auto;
  }
  .pagination-container {
    margin-top: 10px;
    overflow-x: auto;
    padding-bottom: 8px;
  }
  .pagination-container .el-pagination {
    white-space: nowrap;
  }
  /* å“åº”式调整 */
  @media screen and (max-width: 768px) {
    .pagination-container {
      font-size: 12px;
    }
    .pagination-container .el-pagination__sizes {
      margin-right: 8px;
    }
    .pagination-container .el-pagination__jump {
      margin-left: 8px;
    }
    .pagination-container .el-pagination__page-size {
      font-size: 12px;
    }
  }
  .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>