军泰伟业
1.不要树形结构。根据图纸编号为唯一索引添加产品并可以导入
2.要求可以上传图纸
3.产品可以绑定工艺路线
4.工艺路线改成由工序组成,新增编辑是工序可以挪动顺序
5.工序添加报工权限字段,可以多选人员
已添加1个文件
已修改7个文件
已删除2个文件
1528 ■■■■■ 文件已修改
src/api/basicData/productProcess.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 178 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Form.vue 376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productProcess.js
@@ -8,3 +8,11 @@
    params: query
  })
}
// èŽ·å–éƒ¨é—¨ç”¨æˆ·æ ‘
export function listDeptUserTree() {
  return request({
    url: '/productProcess/listDeptUserTree',
    method: 'get'
  })
}
src/views/basicData/product/index.vue
@@ -96,19 +96,17 @@
            clearable
          />
        </el-form-item>
        <el-form-item label="规格型号" prop="drawingNumber">
          <el-input
            v-model="modelForm.drawingNumber"
            placeholder="请输入规格型号"
            clearable
          />
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input
          <el-select
            v-model="modelForm.unit"
            placeholder="请输入单位"
            placeholder="请选择单位"
            clearable
          />
            style="width: 100%"
          >
            <el-option label="ä»¶" value="ä»¶" />
            <el-option label="套" value="套" />
            <el-option label="台" value="台" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品属性" prop="productType">
          <el-select
@@ -121,6 +119,43 @@
            <el-option label="外购" :value="2" />
            <el-option label="委外" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item label="工艺路线" prop="routeId">
          <el-select
            v-model="modelForm.routeId"
            placeholder="请选择工艺路线"
            clearable
            style="width: 100%"
            filterable
          >
            <el-option
              v-for="item in processRouteList"
              :key="item.id"
              :label="item.processRouteName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="上传图纸" prop="drawingFile">
          <el-upload
            v-model:file-list="drawingFileList"
            :action="upload.url"
            :headers="upload.headers"
            :data="upload.data"
            :on-success="handleDrawingUploadSuccess"
            :on-remove="handleDrawingRemove"
            :before-upload="handleDrawingBeforeUpload"
            :limit="1"
            accept=".pdf,.jpg,.jpeg,.png,.dwg"
            list-type="picture-card"
          >
            <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
            <template #tip>
              <div class="el-upload__tip">
                æ”¯æŒ pdf、jpg、jpeg、png、dwg æ ¼å¼ï¼Œå¤§å°ä¸è¶…过 10MB
              </div>
            </template>
          </el-upload>
        </el-form-item>
      </el-form>
    </FormDialog>
@@ -162,10 +197,10 @@
</template>
<script setup>
import { ref, reactive } from "vue";
import { ref, reactive, onMounted } from "vue";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addOrEditProductModel,
@@ -173,6 +208,7 @@
  productListPage,
  downloadTemplate,
} from "@/api/basicData/product.js";
import { listPage as getProcessRouteList } from "@/api/productionManagement/processRoute.js";
import ImportExcel from "./ImportExcel/index.vue";
const { proxy } = getCurrentInstance();
@@ -185,6 +221,14 @@
const tableLoading = ref(false);
const selectedRows = ref([]);
const modelFormRef = ref();
const processRouteList = ref([]);
const drawingFileList = ref([]);
const upload = reactive({
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  headers: { Authorization: "Bearer " + getToken() },
  data: { type: 13 },
});
const queryForm = reactive({
  productName: "",
@@ -210,8 +254,8 @@
    minWidth: 150,
  },
  {
    label: "规格型号",
    prop: "drawingNumber",
    label: "工艺路线",
    prop: "routeName",
    minWidth: 150,
  },
  {
@@ -252,8 +296,11 @@
    productName: "",
    model: "",
    unit: "",
    drawingNumber: "",
    productType: null,
    routeId: null,
    drawingFile: "",
    tempFileIds: [],
    salesLedgerFiles: [],
  },
  modelRules: {
    productName: [
@@ -261,8 +308,7 @@
      { max: 50, message: "产品名称不能超过50个字符", trigger: "blur" },
    ],
    model: [{ required: true, message: "请输入图纸编号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    drawingNumber: [],
    unit: [{ required: true, message: "请选择单位", trigger: "change" }],
    productType: [{ required: true, message: "请选择产品属性", trigger: "change" }],
  },
});
@@ -334,10 +380,27 @@
  modelForm.value.model = "";
  modelForm.value.id = "";
  modelForm.value.unit = "";
  modelForm.value.drawingNumber = "";
  modelForm.value.productType = null;
  modelForm.value.routeId = null;
  modelForm.value.drawingFile = "";
  modelForm.value.tempFileIds = [];
  modelForm.value.salesLedgerFiles = [];
  drawingFileList.value = [];
  if (type === "edit") {
    modelForm.value = { ...data };
    modelForm.value.tempFileIds = data.tempFileIds || [];
    modelForm.value.salesLedgerFiles = data.salesLedgerFiles || [];
    if (data.salesLedgerFiles && data.salesLedgerFiles.length > 0) {
      drawingFileList.value = data.salesLedgerFiles.map(file => ({
        name: file.name,
        url: file.url
      }));
    } else if (data.drawingFile) {
      drawingFileList.value = [{
        name: data.drawingFile.split('/').pop(),
        url: data.drawingFile
      }];
    }
  }
};
@@ -429,7 +492,61 @@
  proxy.download("/basic/product/downloadTemplate", {}, "产品导入模板.xlsx");
};
const getProcessRouteListData = () => {
  getProcessRouteList({ current: 1, size: 1000 }).then((res) => {
    processRouteList.value = res.data.records || [];
  }).catch(() => {
    processRouteList.value = [];
  });
};
const handleDrawingBeforeUpload = (file) => {
  const isAllowed = [
    'application/pdf',
    'image/jpeg',
    'image/jpg',
    'image/png',
    'application/dwg'
  ].includes(file.type) || file.name.endsWith('.dwg');
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isAllowed) {
    proxy.$modal.msgError("只能上传 pdf、jpg、jpeg、png、dwg æ ¼å¼çš„æ–‡ä»¶ï¼");
    return false;
  }
  if (!isLt10M) {
    proxy.$modal.msgError("上传文件大小不能超过 10MB!");
    return false;
  }
  return true;
};
const handleDrawingUploadSuccess = (response, file, fileList) => {
  console.log('上传成功响应', response);
  console.log('response.data', response.data);
  if (response.code === 200) {
    modelForm.value.tempFileIds = [response.data?.tempId];
    modelForm.value.salesLedgerFiles = [{
      tempId: response.data?.tempId,
      originalName: response.data?.originalName || file.name,
      tempPath: response.data?.tempPath,
      type: response.data?.type || 13
    }];
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(response.msg || "上传失败");
  }
};
const handleDrawingRemove = (file) => {
  modelForm.value.tempFileIds = [];
  modelForm.value.salesLedgerFiles = [];
};
onMounted(() => {
getModelList();
  getProcessRouteListData();
});
</script>
<style scoped>
@@ -469,6 +586,31 @@
  margin-top: 16px;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 148px;
  height: 148px;
  text-align: center;
  line-height: 148px;
}
:deep(.el-upload--picture-card) {
  width: 148px;
  height: 148px;
}
:deep(.el-upload-list__item) {
  width: 148px;
  height: 148px;
}
:deep(.el-upload__tip) {
  font-size: 12px;
  color: #909399;
  margin-top: 8px;
}
:deep(.el-dialog__body) {
  padding: 20px 24px;
}
src/views/productionManagement/processRoute/Edit.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/Form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,376 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        :title="isEdit ? '编辑工艺路线' : '创建工艺路线'"
        width="900px"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item label="工艺路线编号">
          <el-input v-model="formState.processRouteCode" placeholder="请输入,忽略将自动生成" clearable />
        </el-form-item>
        <el-form-item
            label="工艺路线名称"
            prop="processRouteName"
            :rules="[
                {
                required: true,
                message: '请输入工艺路线名称',
                trigger: 'blur',
              }
            ]"
        >
          <el-input v-model="formState.processRouteName" placeholder="请填写" clearable />
        </el-form-item>
        <div class="section-title">工序列表</div>
        <div class="table-actions">
          <el-button type="primary" link @click="addRow">
            <el-icon><Plus /></el-icon> æ·»åŠ ä¸€è¡Œ
          </el-button>
        </div>
        <el-table
          ref="tableRef"
          :data="formState.processRouteItems"
          border
          style="width: 100%"
          class="process-table"
          row-key="tempId"
        >
          <el-table-column label="拖拽" width="60" align="center">
            <template #default>
              <el-icon class="drag-handle"><Rank /></el-icon>
            </template>
          </el-table-column>
          <el-table-column label="工序" prop="processId" min-width="200">
            <template #header>
              <span class="required">工序</span>
            </template>
            <template #default="scope">
              <el-select
                v-model="scope.row.processId"
                placeholder="请选择"
                clearable
                style="width: 100%"
              >
                <el-option
                  v-for="item in processOptions"
                  :key="item.id"
                  :label="item.name"
                  :value="item.id"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="是否质检" prop="isQuality" width="100" align="center">
            <template #default="scope">
              <el-switch v-model="scope.row.isQuality" />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="80" align="center" fixed="right">
            <template #default="scope">
              <el-button type="danger" link @click="deleteRow(scope.$index)">
                <el-icon><Delete /></el-icon>
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-empty v-if="formState.processRouteItems.length === 0" description="暂无数据" />
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, getCurrentInstance, onMounted, nextTick, watch, onUnmounted} from "vue";
import {add, update} from "@/api/productionManagement/processRoute.js";
import {findProcessRouteItemList} from "@/api/productionManagement/processRouteItem.js";
import {processList} from "@/api/productionManagement/productionProcess.js";
import {Plus, Delete, Rank} from '@element-plus/icons-vue';
import Sortable from 'sortablejs';
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  }
});
const emit = defineEmits(['update:visible', 'completed']);
const isEdit = computed(() => {
  return formState.value && formState.value.id;
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const processOptions = ref([]);
let { proxy } = getCurrentInstance();
const tableRef = ref(null);
let sortable = null;
let tempIdCounter = 0;
const formState = ref({
  id: undefined,
  processRouteCode: '',
  processRouteName: '',
  processRouteItems: [],
});
const initSortable = () => {
  if (sortable) {
    sortable.destroy();
    sortable = null;
  }
  nextTick(() => {
    if (tableRef.value) {
      const table = tableRef.value.$el.querySelector('.el-table__body-wrapper tbody');
      if (table) {
        sortable = Sortable.create(table, {
          animation: 150,
          handle: '.drag-handle',
          ghostClass: 'sortable-ghost',
          onEnd: (evt) => {
            const { oldIndex, newIndex } = evt;
            if (oldIndex !== undefined && newIndex !== undefined && oldIndex !== newIndex) {
              const item = formState.value.processRouteItems.splice(oldIndex, 1)[0];
              formState.value.processRouteItems.splice(newIndex, 0, item);
            }
          }
        });
      }
    }
  });
};
const getProcessList = () => {
  processList({}).then(res => {
    processOptions.value = res.data || [];
  }).catch(err => {
    console.error("获取工序列表失败:", err);
  });
};
const closeModal = () => {
  formState.value = {
    id: undefined,
    processRouteCode: '',
    processRouteName: '',
    processRouteItems: [],
  };
  isShow.value = false;
};
const setFormData = async () => {
  if (isEdit.value) {
    formState.value = {
      id: props.record.id,
      processRouteCode: props.record.processRouteCode || '',
      processRouteName: props.record.processRouteName || '',
      processRouteItems: (props.record.processRouteItems || []).map((item, index) => ({
        tempId: item.id || `temp_${tempIdCounter++}`,
        processId: item.processId,
        id: item.id,
        isQuality: item.isQuality !== undefined ? item.isQuality : false,
        dragSort: index + 1,
      })),
    };
  } else {
    formState.value = {
      id: undefined,
      processRouteCode: '',
      processRouteName: '',
      processRouteItems: [],
    };
  }
}
const addRow = () => {
  formState.value.processRouteItems.push({
    tempId: `temp_${tempIdCounter++}`,
    processId: undefined,
    isQuality: false,
  });
  nextTick(() => {
    initSortable();
  });
};
const deleteRow = (index) => {
  formState.value.processRouteItems.splice(index, 1);
  nextTick(() => {
    initSortable();
  });
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (formState.value.processRouteItems.length === 0) {
        proxy.$modal.msgError("请至少添加一个工序");
        return;
      }
      for (let i = 0; i < formState.value.processRouteItems.length; i++) {
        const row = formState.value.processRouteItems[i];
        if (!row.processId) {
          proxy.$modal.msgError(`第${i + 1}行:请选择工序`);
          return;
        }
      }
      const submitData = {
        id: formState.value.id,
        processRouteCode: formState.value.processRouteCode,
        processRouteName: formState.value.processRouteName,
        processRouteItems: formState.value.processRouteItems.map((item, index) => ({
          id: item.id,
          routeId: formState.value.id,
          processId: item.processId,
          isQuality: item.isQuality,
          dragSort: index + 1,
        })),
      };
      const apiCall = isEdit.value ? update(submitData) : add(submitData);
      apiCall.then(res => {
        isShow.value = false;
        emit('completed');
        proxy.$modal.msgSuccess(isEdit.value ? "编辑成功" : "新增成功");
      });
    }
  });
};
// ç›‘听 visible å˜åŒ–
watch(() => props.visible, (visible) => {
  if (visible) {
    nextTick(() => {
      initSortable();
    });
  }
});
const setData = async (row) => {
  if (row) {
    formState.value = {
      id: row.id,
      processRouteCode: row.processRouteCode || '',
      processRouteName: row.processRouteName || '',
      processRouteItems: [],
    };
    const res = await findProcessRouteItemList({ routeId: row.id });
    if (res.data && Array.isArray(res.data)) {
      formState.value.processRouteItems = res.data.map((item, index) => ({
        tempId: item.id || `temp_${tempIdCounter++}`,
        processId: item.processId,
        id: item.id,
        isQuality: item.isQuality !== undefined ? item.isQuality : false,
        dragSort: index + 1,
      }));
    }
    nextTick(() => {
      initSortable();
    });
  } else {
    formState.value = {
      id: undefined,
      processRouteCode: '',
      processRouteName: '',
      processRouteItems: [],
    };
    nextTick(() => {
      initSortable();
    });
  }
};
onMounted(() => {
  getProcessList();
});
onUnmounted(() => {
  if (sortable) {
    sortable.destroy();
    sortable = null;
  }
});
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
  setData,
});
</script>
<style scoped>
.section-title {
  font-size: 14px;
  font-weight: bold;
  margin: 20px 0 10px 0;
  color: #333;
}
.table-actions {
  display: flex;
  gap: 16px;
  margin-bottom: 10px;
}
.process-table {
  margin-bottom: 20px;
}
.required::before {
  content: '*';
  color: #f56c6c;
  margin-right: 4px;
}
:deep(.el-dialog__body) {
  padding-top: 10px;
}
.sortable-ghost {
  opacity: 0.4;
  background-color: #f5f7fa;
}
.drag-handle {
  cursor: move;
  font-size: 18px;
  color: #909399;
}
.drag-handle:hover {
  color: #409eff;
}
</style>
src/views/productionManagement/processRoute/New.vue
ÎļþÒÑɾ³ý
src/views/productionManagement/processRoute/index.vue
@@ -2,13 +2,25 @@
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="规格名称:">
          <el-input v-model="searchForm.model" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        <el-form-item label="工艺路线编号">
          <el-input v-model="searchForm.processRouteCode" placeholder="请输入工艺路线编号" clearable style="width: 200px;" />
        </el-form-item>
        <el-form-item label="工艺路线名称">
          <el-input v-model="searchForm.processRouteName" placeholder="请输入工艺路线名称" clearable style="width: 200px;" />
        </el-form-item>
        <el-form-item label="工序名称">
          <el-select v-model="searchForm.processName" placeholder="请选择工序" clearable style="width: 200px;">
            <el-option
              v-for="item in processOptions"
              :key="item.id"
              :label="item.name"
              :value="item.name"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
@@ -29,87 +41,65 @@
          :total="page.total"
      />
    </div>
    <new-process
        v-if="isShowNewModal"
        v-model:visible="isShowNewModal"
        @completed="getList"
    />
    <edit-process
        v-if="isShowEditModal"
        v-model:visible="isShowEditModal"
        :record="record"
        @completed="getList"
    />
    <route-item-form
        v-if="isShowItemModal"
        v-model:visible="isShowItemModal"
        :record="record"
    <process-form
        v-if="isShowFormModal"
        ref="formRef"
        v-model:visible="isShowFormModal"
        @completed="getList"
    />
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import NewProcess from "@/views/productionManagement/processRoute/New.vue";
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import {onMounted, ref, nextTick} from "vue";
import ProcessForm from "@/views/productionManagement/processRoute/Form.vue";
import {listPage, del} from "@/api/productionManagement/processRoute.js";
import { useRouter } from 'vue-router'
import {processList} from "@/api/productionManagement/productionProcess.js";
const router = useRouter()
const data = reactive({
  searchForm: {
    model: "",
    processRouteCode: '',
    processRouteName: '',
    processName: undefined,
  },
});
const { searchForm } = toRefs(data);
const processOptions = ref([]);
const tableColumn = ref([
  {
    label: "工艺路线编号",
    prop: "processRouteCode",
  },
  {
    label: "产品名称",
    prop: "productName",
    label: "工艺路线名称",
    prop: "processRouteName",
  },
    {
        label: "图纸编号",
        prop: "model",
    label: "工序列表",
    prop: "processName",
    minWidth: 300,
    },
  {
    label: "规格名称",
    prop: "drawingNumber",
    label: "创建人",
    prop: "createBy",
  },
  {
    label: "BOM编号",
    prop: "bomNo",
  },
  {
    label: "描述",
    prop: "description",
    label: "创建时间",
    prop: "createTime",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 280,
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          showEditModal(row);
        }
      },
      {
        name: "路线项目",
        type: "text",
        clickFun: (row) => {
          showItemModal(row);
        }
      }
    ]
@@ -118,10 +108,9 @@
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const isShowItemModal = ref(false);
const isShowFormModal = ref(false);
const record = ref({});
const formRef = ref(null);
const page = reactive({
  current: 1,
  size: 100,
@@ -132,6 +121,17 @@
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  searchForm.value = {
    processRouteCode: '',
    processRouteName: '',
    processName: undefined,
  };
  page.current = 1;
  getList();
};
@@ -162,28 +162,14 @@
// æ‰“开新增弹框
const showNewModal = () => {
  isShowNewModal.value = true
  isShowFormModal.value = true;
  record.value = {};
};
const showEditModal = (row) => {
  isShowEditModal.value = true
  record.value = row
};
const showItemModal = (row) => {
  router.push({
    path: '/productionManagement/processRouteItem',
    query: {
      id: row.id,
      processRouteCode: row.processRouteCode || '',
      drawingNumber: row.drawingNumber || "",
      productName: row.productName || '',
      model: row.model || '',
      bomNo: row.bomNo || '',
      description: row.description || '',
      type: 'route',
    }
  })
const showEditModal = async (row) => {
  isShowFormModal.value = true;
  await nextTick();
  await formRef.value && formRef.value.setData({ id: row.id, processRouteCode: row.processRouteCode, processRouteName: row.processRouteName });
};
// åˆ é™¤
@@ -202,8 +188,18 @@
}
onMounted(() => {
  getProcessList();
  getList();
});
// èŽ·å–å·¥åºåˆ—è¡¨
const getProcessList = () => {
  processList({}).then(res => {
    processOptions.value = res.data || [];
  }).catch(err => {
    console.error("获取工序列表失败:", err);
  });
};
</script>
<style scoped></style>
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -96,6 +96,11 @@
          {{scope.row.isQuality ? "是" : "否"}}
        </template>
      </el-table-column>
      <el-table-column label="报工权限" prop="userPower" min-width="200">
        <template #default="scope">
          {{ scope.row.userPower || '-' }}
        </template>
      </el-table-column>
      <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>
@@ -207,7 +212,22 @@
        </el-form-item>
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/>
          <el-switch v-model="form.isQuality" :active-value="true" :inactive-value="false"/>
        </el-form-item>
        <el-form-item label="报工权限" prop="userPower" :rules="[{ required: true, message: '请选择报工权限', trigger: 'change' }]">
          <el-tree-select
            v-model="form.userPower"
            :data="staffList"
            :props="treeProps"
            placeholder="请选择人员"
            multiple
            show-checkbox
            collapse-tags
            collapse-tags-tooltip
            style="width: 100%"
            node-key="id"
            :render-after-expand="false"
          />
        </el-form-item>
      </el-form>
@@ -232,6 +252,7 @@
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 { listDeptUserTree } from "@/api/basicData/productProcess.js";
import { useRoute } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import Sortable from 'sortablejs'
@@ -263,6 +284,13 @@
const processOptions = ref([]);
const showProductSelectDialog = ref(false);
const staffList = ref([]);
const treeProps = {
  label: 'label',
  children: 'children',
};
let tableSortable = null;
let cardSortable = null;
@@ -284,6 +312,7 @@
  model: "",
  unit: "",
  isQuality: false,
  userPower: [],
});
const rules = {
@@ -356,6 +385,12 @@
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  const userPowerNames = row.userPower ? row.userPower.split(',') : [];
  const userPowerIds = userPowerNames.map(name => {
    const user = findUserByName(name);
    return user ? user.id : null;
  }).filter(id => id !== null);
  form.value = {
    id: row.id,
    routeId: routeId.value,
@@ -365,8 +400,25 @@
    model: row.model || "",
    unit: row.unit || "",
    isQuality: row.isQuality,
    userPower: userPowerIds,
  };
  dialogVisible.value = true;
};
const findUserByName = (name) => {
  const findInTree = (nodes) => {
    for (const node of nodes) {
      if (node.isUser && node.label === name) {
        return node;
      }
      if (node.children && node.children.length > 0) {
        const found = findInTree(node.children);
        if (found) return found;
      }
    }
    return null;
  };
  return findInTree(staffList.value);
};
// åˆ é™¤
@@ -415,6 +467,8 @@
    if (valid) {
      submitLoading.value = true;
      
      const userPowerNames = getAllUserNamesFromSelection(form.value.userPower);
      if (operationType.value === 'add') {
        // æ–°å¢žï¼šä¼ å•个对象,包含dragSort字段
        // dragSort = å½“前列表长度 + 1,表示新增记录排在最后
@@ -428,6 +482,7 @@
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
              userPower: userPowerNames.join(','),
              dragSort,
            })
          : addOrUpdateProcessRouteItem({
@@ -435,6 +490,7 @@
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
              userPower: userPowerNames.join(','),
              dragSort,
            });
@@ -460,6 +516,7 @@
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              isQuality: form.value.isQuality,
              userPower: userPowerNames.join(','),
            })
          : addOrUpdateProcessRouteItem({
              routeId: routeId.value,
@@ -467,6 +524,7 @@
              productModelId: form.value.productModelId,
              id: form.value.id,
              isQuality: form.value.isQuality,
              userPower: userPowerNames.join(','),
            });
        updatePromise
@@ -640,8 +698,70 @@
  getRouteInfo();
  getList();
  getProcessList();
  getStaffList();
});
const getStaffList = () => {
  listDeptUserTree().then(res => {
    const buildTree = (nodes) => {
      return nodes.map(node => {
        const deptNode = {
          id: `dept_${node.deptId}`,
          label: node.deptName,
          isUser: false,
          children: []
        };
        if (node.userList && node.userList.length > 0) {
          node.userList.forEach(user => {
            deptNode.children.push({
              id: user.userId,
              label: user.nickName || user.userName,
              isUser: true,
              userName: user.userName,
              nickName: user.nickName
            });
          });
        }
        if (node.childrenList && node.childrenList.length > 0) {
          const childNodes = buildTree(node.childrenList);
          deptNode.children = deptNode.children.concat(childNodes);
        }
        return deptNode;
      });
    };
    staffList.value = buildTree(res.data || []);
  }).catch(() => {
    staffList.value = [];
  });
};
const getAllUserNamesFromSelection = (selectedIds) => {
  const names = [];
  const processNode = (node) => {
    if (selectedIds.includes(node.id)) {
      if (node.isUser) {
        names.push(node.label);
      } else {
        if (node.children && node.children.length > 0) {
          node.children.forEach(child => {
            if (child.isUser) {
              names.push(child.label);
            }
          });
        }
      }
    }
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => processNode(child));
    }
  };
  staffList.value.forEach(node => processNode(node));
  return [...new Set(names)];
};
onUnmounted(() => {
  destroySortable();
});
src/views/productionManagement/productionProcess/Edit.vue
@@ -31,6 +31,21 @@
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
        </el-form-item>
        <el-form-item label="报工权限" prop="userPower" :rules="[{ required: true, message: '请选择报工权限', trigger: 'change' }]">
          <el-tree-select
            v-model="formState.userPower"
            :data="staffList"
            :props="treeProps"
            placeholder="请选择人员"
            multiple
            show-checkbox
            collapse-tags
            collapse-tags-tooltip
            style="width: 100%"
            node-key="id"
            :render-after-expand="false"
          />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
@@ -46,8 +61,9 @@
</template>
<script setup>
import { ref, computed, getCurrentInstance, watch } from "vue";
import { ref, computed, getCurrentInstance, watch, onMounted } from "vue";
import {update} from "@/api/productionManagement/productionProcess.js";
import { listDeptUserTree } from "@/api/basicData/productProcess.js";
const props = defineProps({
  visible: {
@@ -71,7 +87,15 @@
  remark: props.record.remark,
  salaryQuota: props.record.salaryQuota,
  isQuality: props.record.isQuality,
  userPower: props.record.userPower ? props.record.userPower.split(',') : [],
});
const staffList = ref([]);
const treeProps = {
  label: 'label',
  children: 'children',
};
const isShow = computed({
  get() {
@@ -85,30 +109,23 @@
// ç›‘听 record å˜åŒ–,更新表单数据
watch(() => props.record, (newRecord) => {
  if (newRecord && isShow.value) {
    const userPowerNames = newRecord.userPower ? newRecord.userPower.split(',') : [];
    const userPowerIds = userPowerNames.map(name => {
      const user = findUserByName(name);
      return user ? user.id : null;
    }).filter(id => id !== null);
    formState.value = {
      id: newRecord.id,
      name: newRecord.name || '',
      no: newRecord.no || '',
      remark: newRecord.remark || '',
      salaryQuota: newRecord.salaryQuota || '',
      isQuality: props.record.isQuality,
      isQuality: newRecord.isQuality,
      userPower: userPowerIds,
    };
  }
}, { immediate: true, deep: true });
// ç›‘听弹窗打开,重新初始化表单数据
watch(() => props.visible, (visible) => {
  if (visible && props.record) {
    formState.value = {
      id: props.record.id,
      name: props.record.name || '',
      no: props.record.no || '',
      remark: props.record.remark || '',
      salaryQuota: props.record.salaryQuota || '',
      isQuality: props.record.isQuality,
    };
  }
});
let { proxy } = getCurrentInstance()
@@ -119,7 +136,13 @@
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      update(formState.value).then(res => {
      const userPowerNames = getAllUserNamesFromSelection(formState.value.userPower);
      const submitData = {
        ...formState.value,
        userPower: userPowerNames.join(',')
      };
      update(submitData).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
@@ -130,6 +153,87 @@
  })
};
const findUserById = (userId) => {
  const findInTree = (nodes) => {
    for (const node of nodes) {
      if (node.id === userId) {
        return node;
      }
      if (node.children && node.children.length > 0) {
        const found = findInTree(node.children);
        if (found) return found;
      }
    }
    return null;
  };
  return findInTree(staffList.value);
};
const getStaffList = () => {
  listDeptUserTree().then(res => {
    const buildTree = (nodes) => {
      return nodes.map(node => {
        const deptNode = {
          id: `dept_${node.deptId}`,
          label: node.deptName,
          isUser: false,
          children: []
        };
        if (node.userList && node.userList.length > 0) {
          node.userList.forEach(user => {
            deptNode.children.push({
              id: user.userId,
              label: user.nickName || user.userName,
              isUser: true,
              userName: user.userName,
              nickName: user.nickName
            });
          });
        }
        if (node.childrenList && node.childrenList.length > 0) {
          const childNodes = buildTree(node.childrenList);
          deptNode.children = deptNode.children.concat(childNodes);
        }
        return deptNode;
      });
    };
    staffList.value = buildTree(res.data || []);
  }).catch(() => {
    staffList.value = [];
  });
};
const getAllUserNamesFromSelection = (selectedIds) => {
  const names = [];
  const processNode = (node) => {
    if (selectedIds.includes(node.id)) {
      if (node.isUser) {
        names.push(node.label);
      } else {
        if (node.children && node.children.length > 0) {
          node.children.forEach(child => {
            if (child.isUser) {
              names.push(child.label);
            }
          });
        }
      }
    }
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => processNode(child));
    }
  };
  staffList.value.forEach(node => processNode(node));
  return [...new Set(names)];
};
onMounted(() => {
  getStaffList();
});
defineExpose({
  closeModal,
  handleSubmit,
src/views/productionManagement/productionProcess/New.vue
@@ -29,7 +29,22 @@
<!--          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />-->
<!--        </el-form-item>-->
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
          <el-switch v-model="formState.isQuality" :active-value="true" :inactive-value="false"/>
        </el-form-item>
        <el-form-item label="报工权限" prop="userPower" :rules="[{ required: true, message: '请选择报工权限', trigger: 'change' }]">
          <el-tree-select
            v-model="formState.userPower"
            :data="staffList"
            :props="treeProps"
            placeholder="请选择人员"
            multiple
            show-checkbox
            collapse-tags
            collapse-tags-tooltip
            style="width: 100%"
            node-key="id"
            :render-after-expand="false"
          />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
@@ -46,8 +61,9 @@
</template>
<script setup>
import { ref, computed, getCurrentInstance } from "vue";
import { ref, computed, getCurrentInstance, onMounted } from "vue";
import {add} from "@/api/productionManagement/productionProcess.js";
import { listDeptUserTree } from "@/api/basicData/productProcess.js";
const props = defineProps({
  visible: {
@@ -64,7 +80,15 @@
  remark: '',
  salaryQuota:  '',
  isQuality: false,
  userPower: [],
});
const staffList = ref([]);
const treeProps = {
  label: 'label',
  children: 'children',
};
const isShow = computed({
  get() {
@@ -84,7 +108,13 @@
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      add(formState.value).then(res => {
      const userPowerNames = getAllUserNamesFromSelection(formState.value.userPower);
      const submitData = {
        ...formState.value,
        userPower: userPowerNames.join(',')
      };
      add(submitData).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
@@ -95,6 +125,87 @@
  })
};
const findUserById = (userId) => {
  const findInTree = (nodes) => {
    for (const node of nodes) {
      if (node.id === userId) {
        return node;
      }
      if (node.children && node.children.length > 0) {
        const found = findInTree(node.children);
        if (found) return found;
      }
    }
    return null;
  };
  return findInTree(staffList.value);
};
const getStaffList = () => {
  listDeptUserTree().then(res => {
    const buildTree = (nodes) => {
      return nodes.map(node => {
        const deptNode = {
          id: `dept_${node.deptId}`,
          label: node.deptName,
          isUser: false,
          children: []
        };
        if (node.userList && node.userList.length > 0) {
          node.userList.forEach(user => {
            deptNode.children.push({
              id: user.userId,
              label: user.nickName || user.userName,
              isUser: true,
              userName: user.userName,
              nickName: user.nickName
            });
          });
        }
        if (node.childrenList && node.childrenList.length > 0) {
          const childNodes = buildTree(node.childrenList);
          deptNode.children = deptNode.children.concat(childNodes);
        }
        return deptNode;
      });
    };
    staffList.value = buildTree(res.data || []);
  }).catch(() => {
    staffList.value = [];
  });
};
const getAllUserNamesFromSelection = (selectedIds) => {
  const names = [];
  const processNode = (node) => {
    if (selectedIds.includes(node.id)) {
      if (node.isUser) {
        names.push(node.label);
      } else {
        if (node.children && node.children.length > 0) {
          node.children.forEach(child => {
            if (child.isUser) {
              names.push(child.label);
            }
          });
        }
      }
    }
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => processNode(child));
    }
  };
  staffList.value.forEach(node => processNode(node));
  return [...new Set(names)];
};
onMounted(() => {
  getStaffList();
});
defineExpose({
  closeModal,
  handleSubmit,
src/views/productionManagement/productionProcess/index.vue
@@ -98,6 +98,11 @@
      label: "工序名称",
      prop: "name",
    },
    {
      label: "报工权限",
      prop: "userPower",
      width: 200,
    },
    // {
    //   label: "工资定额",
    //   prop: "salaryQuota",