chenhj
11 小时以前 de4a1d478c988d6b2f1fd41011b144c03c996b96
Merge branch 'dev_JTWY' of http://114.132.189.42:9002/r/TianJin-product-management into dev_JTWY
已添加3个文件
已修改4个文件
379 ■■■■■ 文件已修改
src/api/basicData/productModel.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productModel.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from "@/utils/request.js";
export function productModelList(query) {
    return request({
        url: '/basic/product/pageModel',
        method: 'get',
        params: query
    })
}
src/api/productionManagement/processRoute.js
@@ -1,4 +1,4 @@
// å·¥åºé¡µé¢æŽ¥å£
// å·¥è‰ºè·¯çº¿é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
src/api/productionManagement/processRouteItem.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProcessRouteItemList(query) {
    return request({
        url: "/processRouteItem/list",
        method: "get",
        params: query,
    });
}
export function addOrUpdateProcessRouteItem(data) {
    return request({
        url: "/processRouteItem",
        method: "post",
        data: data,
    });
}
src/api/productionManagement/productionProcess.js
@@ -10,6 +10,14 @@
  });
}
export function processList(query) {
  return request({
    url: "/productProcess/list",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/productProcess",
src/views/basicData/product/ProductSelectDialog.vue
@@ -37,8 +37,10 @@
        :data="tableData"
        height="420"
        highlight-current-row
        @current-change="onCurrentChange"
        row-key="id"
        @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="#" width="60"/>
      <el-table-column prop="productName" label="产品大类" min-width="160"/>
      <el-table-column prop="model" label="型号名称" min-width="200"/>
@@ -60,7 +62,7 @@
    <template #footer>
      <el-button @click="close()">取消</el-button>
      <el-button type="primary" :disabled="!selectedRow" @click="onConfirm">
      <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
        ç¡®å®š
      </el-button>
    </template>
@@ -70,7 +72,7 @@
<script setup lang="ts">
import {computed, onMounted, reactive, ref, watch} from "vue";
import {ElMessage} from "element-plus";
import {list} from '@/api/basicData/productModel'
import {productModelList} from '@/api/basicData/productModel'
export type ProductRow = {
  id: number;
@@ -83,10 +85,7 @@
  modelValue: boolean;
}>();
const emit = defineEmits<{
  (e: "update:modelValue", v: boolean): void;
  (e: "confirm", row: ProductRow): void; // æŠŠæ•´è¡Œæ•°æ®è¿”给父组件
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = computed({
  get: () => props.modelValue,
@@ -106,14 +105,14 @@
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const selectedRow = ref<ProductRow | null>(null);
const multipleSelection = ref<ProductRow[]>([])
function close() {
  visible.value = false;
}
function onCurrentChange(row: ProductRow | null) {
  selectedRow.value = row;
const handleSelectionChange = (val: ProductRow[]) => {
  multipleSelection.value = val
}
function onSearch() {
@@ -133,25 +132,25 @@
}
function onConfirm() {
  if (!selectedRow.value) {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning("请选择一条产品");
    return;
  }
  emit("confirm", selectedRow.value);
  emit("confirm", multipleSelection.value);
  close();
}
async function loadData() {
  loading.value = true;
  try {
    selectedRow.value = null; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res = await list({
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      pageNum: page.pageNum,
      pageSize: page.pageSize,
    });
    tableData.value = res.list;
    tableData.value = res.records;
    total.value = res.total;
  } finally {
    loading.value = false;
src/views/productionManagement/processRoute/ItemsForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,285 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="工艺路线项目"
        width="800px"
        @close="closeModal"
    >
      <el-button
          type="primary"
          @click="isShowProductSelectDialog = true"
          class="mb5"
          style="margin-bottom: 10px;"
      >
        é€‰æ‹©äº§å“
      </el-button>
      <el-table
          ref="multipleTable"
          v-loading="tableLoading"
          border
          :data="routeItems"
          :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
          row-key="id"
          tooltip-effect="dark"
          class="lims-table"
          style="cursor: move;"
      >
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
            v-for="(item, index) in tableColumn"
            :key="index"
            :label="item.label"
            :width="item.width"
            show-overflow-tooltip
        >
          <template #default="scope" v-if="item.dataType === 'action'">
            <el-button
                v-for="(op, opIndex) in item.operation"
                :key="opIndex"
                :type="op.type"
                :link="op.link"
                size="small"
                @click.stop="op.clickFun(scope.row)"
            >
              {{ op.name }}
            </el-button>
          </template>
          <template #default="scope" v-else>
            <template v-if="item.prop === 'processId'">
              <el-select
                  v-model="scope.row[item.prop]"
                  style="width: 100%;"
                  @mousedown.stop
              >
                <el-option
                    v-for="process in processOptions"
                    :key="process.id"
                    :label="process.name"
                    :value="process.id"
                />
              </el-select>
            </template>
            <template v-else>
              {{ scope.row[item.prop] || '-' }}
            </template>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <ProductSelectDialog
        v-model="isShowProductSelectDialog"
        @confirm="handelSelectProducts"
    />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import Sortable from 'sortablejs';
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
    default: false
  },
  record: {
    type: Object,
    required: true,
    default: () => ({})
  }
});
const emit = defineEmits(['update:visible', 'completed']);
const processOptions = ref([]);
const tableLoading = ref(false);
const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let sortable = null;
const multipleTable = ref(null);
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  }
});
const tableColumn = ref([
  { label: "产品名称", prop: "productName", width: 180 },
  { label: "规格名称", prop: "model", width: 150 },
  { label: "单位", prop: "unit", width: 80 },
  { label: "工序名称", prop: "processId", width: 180 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 100,
    operation: [
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          const idx = routeItems.value.findIndex(item => item.id === row.id);
          if (idx > -1) {
            routeItems.value.splice(idx, 1);
          }
        }
      }
    ]
  }
]);
const closeModal = () => {
  isShow.value = false;
};
const handelSelectProducts = (products) => {
  const newData = products.map(({ id, ...product }) => ({
    ...product,
    productModelId: id,
    routeId: props.record.id,
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, // ç”Ÿæˆæ— ç‰¹æ®Šå­—符的ID
    processId: undefined
  }));
  routeItems.value.push(...newData);
  nextTick(() => initSortable());
};
const findProcessRouteItems = () => {
  tableLoading.value = true;
  findProcessRouteItemList({ routeId: props.record.id })
      .then(res => {
        tableLoading.value = false;
        routeItems.value = res.data.map(item => ({
          ...item,
          processId: item.processId === 0 ? undefined : item.processId
        }));
        nextTick(() => initSortable());
      })
      .catch(err => {
        tableLoading.value = false;
        console.error("获取列表失败:", err);
      });
};
const findProcessList = () => {
  processList({})
      .then(res => {
        processOptions.value = res.data;
      })
      .catch(err => {
        console.error("获取工序失败:", err);
      });
};
const { proxy } = getCurrentInstance() || {};
const handleSubmit = () => {
  if (routeItems.value.length === 0) {
    proxy?.$modal?.msgError("请添加路线项目");
    return;
  }
  const hasEmptyProcess = routeItems.value.some(item => !item.processId);
  if (hasEmptyProcess) {
    proxy?.$modal?.msgError("请为所有项目选择工序");
    return;
  }
  addOrUpdateProcessRouteItem({
    routeId: props.record.id,
    processRouteItem: routeItems.value.map(({ id, ...item }) => item)
  })
      .then(res => {
        isShow.value = false;
        emit('completed');
        proxy?.$modal?.msgSuccess("提交成功");
      })
      .catch(err => {
        proxy?.$modal?.msgError(`提交失败:${err.msg || "网络异常"}`);
      });
};
const initSortable = () => {
  if (sortable) {
    sortable.destroy();
    sortable = null;
  }
  if (!multipleTable.value) return;
  const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
      multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
  if (!tbody) return;
  sortable = new Sortable(tbody, {
    animation: 150,
    ghostClass: 'sortable-ghost',
    handle: '.el-table__row',
    filter: '.el-button, .el-select',
    onEnd: (evt) => {
      const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
      routeItems.value.splice(evt.newIndex, 0, moveItem);
    }
  });
};
onMounted(() => {
  findProcessRouteItems();
  findProcessList();
});
onUnmounted(() => {
  if (sortable) {
    sortable.destroy();
  }
});
// ä¿®å¤ï¼šæš´éœ²æ–¹æ³•时避免语法错误
defineExpose({
  closeModal,
  handleSubmit,
  isShow
});
</script>
<style scoped>
:deep(.sortable-ghost) {
  opacity: 0.6;
  background-color: #f5f7fa !important;
}
:deep(.el-table__row) {
  transition: background-color 0.2s;
}
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
.mb5 {
  margin-bottom: 5px;
}
</style>
src/views/productionManagement/processRoute/index.vue
@@ -2,8 +2,8 @@
    <div class="app-container">
        <div class="search_form">
            <el-form :model="searchForm" :inline="true">
                <el-form-item label="零件名称:">
                    <el-input v-model="searchForm.speculativeTradingName" placeholder="请输入" clearable prefix-icon="Search"
                <el-form-item label="规格名称:">
                    <el-input v-model="searchForm.model" placeholder="请输入" clearable prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
@@ -27,7 +27,7 @@
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            ></PIMTable>
            />
        </div>
        <new-process
      v-if="isShowNewModal"
@@ -41,6 +41,14 @@
      :record="record"
      @completed="getList"
    />
    <route-item-form
        v-if="isShowItemModal"
        v-model:visible="isShowItemModal"
        :record="record"
        @completed="getList"
    />
    RouteItemForm
    </div>
</template>
@@ -48,6 +56,7 @@
import {onMounted, ref} from "vue";
import NewProcess from "@/views/productionManagement/processRoute/New.vue";
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import {listPage, del} from "@/api/productionManagement/processRoute.js";
const data = reactive({
@@ -73,17 +82,17 @@
    width: 280,
    operation: [
      {
        name: "详情",
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          showEditModal(row);
        }
      },
      {
        name: "编辑",
        name: "路线项目",
        type: "text",
        clickFun: (row) => {
          showEditModal(row);
          showItemModal(row);
        }
      }
    ]
@@ -94,6 +103,7 @@
const tableLoading = ref(false);
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const isShowItemModal = ref(false);
const record = ref({});
const page = reactive({
    current: 1,
@@ -143,6 +153,11 @@
  record.value = row
};
const showItemModal = (row) => {
  isShowItemModal.value = true
  record.value = row
};
// åˆ é™¤
function handleDelete() {
  const ids = selectedRows.value.map((item) => item.id);