廖井涛
7 天以前 a660db06773007b1be690e0674829c00a57aeb7b
north-glass-erp/northglass-erp/src/views/sd/bom/ProductBomAdd.vue
@@ -1,10 +1,44 @@
<script setup>
import {reactive, ref} from "vue";
import { reactive, ref, computed, watch,onMounted } from "vue";
import {useI18n} from "vue-i18n";
import request from "@/utils/request"
import {changeFilterEvent, filterChanged} from "@/hook";
import deepClone from "@/utils/deepClone"
import { ElMessage, ElMessageBox} from "element-plus"
const { t } = useI18n()
const emit = defineEmits(['closeDialog'])
let produceList = ref([])
let props = defineProps({
  productName:null,
  produceId:null,
  baseGridOptions: { type: Object, required: true },
  gridDataByProduct: { type: Object, default: () => ({}) }
})
const xGrid = ref()
const xGridByProduct = ref()
const activeProduct = ref('')                 // 当前选中的产品名
let materialType = ref(1)
//关闭弹窗
const closeDialog = () => {
  emit('closeDialog')
}
const value = ref('')
const options = [
  {
    value: t('ingredients.originalFilm'),
    label: t('ingredients.originalFilm')
  },
  {
    value: t('ingredients.accessories'),
    label: t('ingredients.accessories'),
  }
]
const gridOptions = reactive({
  border:  "full",//表格加边框
  keepSource: true,//保持源数据
@@ -26,25 +60,9 @@
  customConfig: {
    storage: true
  },
  editConfig: {
    trigger: 'click',
    mode: 'row',
    showStatus: true
  },
  columns:[
    {field:'id',title: '物料编号', },
    {field: 'name', title: '物料名称'},
    {field: 'thickness', title: '厚度'},
    {field: 'size', title: '尺寸'},
    {field:'consumption',title: '消耗量' },
    {field:'unit',title: '单位' },
    {field:'price',title: '价格' }
  ],//表头参数
  data:[
    {id:'1',name:'6mm超白',thickness:6,size:3660*2440,consumption:'1.2',unit:'/㎡',price:'60'},
    {id:'2',name:'6mmPDE60A03',thickness:6,size:3660*3300,consumption:'1.17',unit:'/㎡',price:'70'},
    {id:'3',name:'7mmPDE60A03',thickness:7,size:4280*3050,consumption:'1.21',unit:'/㎡',price:'75'},
    {id:'4',name:'8mm超白US1.16',thickness:6,size:5800*3300,consumption:'1.3',unit:'/㎡',price:'90'}
  ],//表格数据
  toolbarConfig: {
    slots:{
@@ -80,16 +98,18 @@
    showStatus: true
  },
  columns:[
    {field:'id',title: '物料编号', },
    {field: 'name', title: '物料名称'},
    {field: 'thickness', title: '厚度'},
    {field: 'size', title: '尺寸'},
    {field:'consumption',title: '消耗量' },
    {field:'unit',title: '单位' },
    {field:'price',title: '价格' }
    {title: '', width: '110', slots: { default: 'button_slot' },fixed:'left'},
    {field:'id',title: t('ingredients.materialCode') },
    {field: 'name', title: t('ingredientsStock.materialName')},
    {field: 'thickness', title: t('product.msg.allThickness')},
    {field: 'width', title: t('order.width')},
    {field: 'height', title: t('order.height')},
    {field:'consume',title: t('bom.consume') },
    {field:'unit',title: t('ingredients.unit') },
    {field:'price',title: t('bom.price1') }
  ],//表头参数
  data:[
    {id:'1',name:'6mm超白',thickness:6,size:3660*2440,consumption:'1.2',unit:'/㎡',price:'60'}
  ],//表格数据
  toolbarConfig: {
    slots: {
@@ -98,20 +118,252 @@
  }
})
let arr = [
  {title: t('basicData.add'), width: '110', slots: { default: 'button_slot' },fixed:'left'},
  {field: 'tabId', width: '150',title: 'BOMId', sortable: true,showOverflow:'ellipsis' ,filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
  {field: 'id', width: '150',title: t('ingredients.materialCode'), sortable: true,showOverflow:'ellipsis' ,filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged},
  {field: 'consume',title: t('bom.consume'),showOverflow:'ellipsis' },
  {field: 'price',title: t('bom.price'),showOverflow:'ellipsis' }
]
const bomTitle = [
]
let pageNum=ref(1)
let total = reactive({
  pageTotal : 0,
  dataTotal : 0,
  pageSize : 1000
})
let filterData = ref({
  type:''
})
let BasicData = ref([])
let materialStore= ref([])
onMounted(async () => {
  //第一次加载默认
  value.value=t('ingredients.originalFilm')
  filterData.value.type=t('ingredients.originalFilm')
  request.get(`/BasicWarehouse/BasicWarehouseType/${value.value}`).then((res) => {
    if(res.code==200){
      gridOptions.columns.splice(0,gridOptions.columns.length)
      BasicData.value = res.data
      //添加列
      gridOptions.columns=arr.slice()
      for (let i=0;i<BasicData.value.length;i++){
        let aa={field: BasicData.value[i].OperateType, width: '150',title: BasicData.value[i].OperateTypeName, sortable: true,showOverflow:'ellipsis' ,filters:[{ data: '' }],slots: { filter: 'num1_filter' },filterMethod:filterChanged}
        gridOptions.columns.push(aa)
      }
      bomTitle.forEach((item) => {
        gridOptions.columns.push(item)
      })
      getWorks()
      getDetails();
      editProductBOM();
    }else{
      ElMessage.warning(res.msg)
    }
  })
})
//列查询
const getWork = () => {
  filterData.value.type=value.value
  request.get(`/BasicWarehouse/BasicWarehouseType/${value.value}`).then((res) => {
    if(res.code==200){
      gridOptions.columns=[]
      BasicData.value = res.data
      //添加列
      gridOptions.columns=arr.slice()
      for (let i=0;i<BasicData.value.length;i++){
        let column={field: BasicData.value[i].OperateType,
          width: '150',title: BasicData.value[i].OperateTypeName,
          sortable: true,showOverflow:'ellipsis' ,
          filters:[{ data: '' }],
          slots: { filter: 'num1_filter' },
          filterMethod:filterChanged}
        gridOptions.columns.push(column)
      }
      bomTitle.forEach((item) => {
        gridOptions.columns.push(item)
      })
      getWorks()
    }else{
      ElMessage.warning(res.msg)
    }
  })
}
//数据绑定
const getWorks = () => {
  request.post(`/materialStore/getSelectProductBOM/1/${total.pageSize}`,filterData.value).then((res) => {
    if(res.code==200){
      materialStore.value=[]
      for (let i=0;i<res.data.data.length;i++){
        materialStore.value[i]= JSON.parse(res.data.data[i].json)
        materialStore.value[i].id= res.data.data[i].id
        materialStore.value[i].consume= res.data.data[i].consume
        materialStore.value[i].tabId= res.data.data[i].tabId
        switch (res.data.data[i].bomType) {
          case '1':
            materialStore.value[i].bomType = t('order.area');
            break;
          case '2':
            materialStore.value[i].bomType = t('order.perimeter');
            break;
          case "3":
            materialStore.value[i].bomType = t('order.quantity');
            break;
          default:
            materialStore.value[i].bomType = res.data.data[i].bomType; // 保留原值
        }
        materialStore.value[i].price= res.data.data[i].price
      }
      total.dataTotal = res.data.total.total*1
      total.pageTotal= res.data.total.pageTotal
      pageNum.value=1
      produceList = deepClone(materialStore.value)
      xGrid.value.loadData(produceList)
      gridOptions.loading=false
    }else{
      ElMessage.warning(res.msg)
      router.push("/login")
    }
  })
}
const getDetails = () => {
}
//拆分每个产品
const productList = computed(() => {
  const raw = (props.productName ?? '').toString().trim()
  if (!raw) return [t('bom.other')]
  const parts = raw
      .split(/[+*]/)
      .map(s => s.trim())
      .filter(Boolean)
  parts.push(t('bom.other'))
  return parts
})
// 当前选中 tab 的下标
const activeProductIndex = ref(null)
const gridDataMapByProduct = reactive({})
const handleTabClick = (name, index) => {
  activeProductIndex.value = index
  if (!gridDataMapByProduct[index]) {
    gridDataMapByProduct[index] = [] // 初始化
  }
  xGridByProduct.value?.loadData(gridDataMapByProduct[index])
}
const getTableRow = (row, type) => {
  if (type !== 'add') return
  const key = activeProductIndex.value
  if (key=="" && key==null) {
    ElMessage.warning(t('bom.msg.msg1'))
    return
  }
  const plainRow = JSON.parse(JSON.stringify(row))
  const oldData = (gridDataMapByProduct[key] || []).map((item) =>
      JSON.parse(JSON.stringify(item))
  )
  gridDataMapByProduct[key] = [...oldData, plainRow]
  xGridByProduct.value.loadData(gridDataMapByProduct[key])
}
const getTableRowDils = (row,type) =>{
  switch (type) {
    case 'delete' :{
      const key = activeProductIndex.value
      gridDataMapByProduct[key] = (gridDataMapByProduct[key] || []).filter(
          item => item.id !== row.id
      )
      xGridByProduct.value.loadData(gridDataMapByProduct[key])
      return
    }
  }
}
const saveProductBOM = () => {
  gridOptionsByProduct.loading=true
  productList.value.forEach((name, index) => {
    const rows = gridDataMapByProduct[index] || []
    const seq = index + 1
   //给每条数据加上对应层号、产品编号
    rows.forEach(row => {
      row.layer = seq
      row.produceId = props.produceId
    })
  })
  request.post(`/BomData/saveProductBOM`,gridDataMapByProduct).then((res) => {
    if(res.code==200){
      ElMessage.success(t('basicData.msg.saveSuccess'))
      gridOptionsByProduct.loading=false
      emit('closeDialog')
    }})
}
const editProductBOM = () => {
  request.post(`/BomData/editProductBOM/${props.produceId}`).then((res) => {
    if (res.code == 200 ) {
      // 初始化清空
      productList.value.forEach((_, index) => {
        gridDataMapByProduct[index + 1] = []
      })
      // 遍历后端返回的数据
      res.data.data.forEach(item => {
        const layer = item.product_layer - 1  // 例如 1, 2, 3
        if (!gridDataMapByProduct[layer]) {
          gridDataMapByProduct[layer] = []
        }
        const plainItem = JSON.parse(JSON.stringify(item))
        gridDataMapByProduct[layer].push(plainItem)
      })
      // 默认选择第一层
      activeProductIndex.value = 0
      xGridByProduct.value.loadData(gridDataMapByProduct[0])
    }
  })
}
</script>
<template>
  <div id="main_add">
    <div class="product">
      <el-card style="max-width: 480px">
        <p  class="text item">6mm超白钢化</p>
        <p  class="text item">12AR</p>
        <p style="color: blue" class="text item">6mm超白钢化</p>
        <p  class="text item">0.76PVB</p>
        <p  class="text item">6mm超白钢化</p>
        <p  class="text item">其他</p>
      <el-card class="product-card" shadow="never">
        <div class="product-tabs">
          <p
              v-for="(name, i) in productList"
              :key="i"
              class="text item tab"
              :class="{ active: activeProductIndex === i }"
              @click="handleTabClick(name, i)"
          >
            {{ name }}
          </p>
        </div>
      </el-card>
    </div>
    <div class="productLayer">
@@ -121,10 +373,16 @@
          height="100%"
          size="small"
          v-bind="gridOptionsByProduct"
      >
        <template #toolbar_buttons>
          <el-button  >删除</el-button>
          <el-button type="primary" >保存</el-button>
          <el-button type="primary" @click="saveProductBOM">保存</el-button>
        </template>
        <template #button_slot="{ row }">
          <el-button @click="getTableRowDils(row,'delete')"
                     link type="primary" size="small">{{ $t('basicData.delete') }}</el-button>
        </template>
      </vxe-grid>
@@ -138,10 +396,27 @@
          size="small"
      >
        <template #toolbar_buttons>
          <vxe-select  v-model="materialType">
            <vxe-option :value="1" :label="'原片'"></vxe-option>
            <vxe-option :value="2" :label="'辅料'"></vxe-option>
          </vxe-select>
          <el-select v-model="value" style="width: 150px;" :placeholder="$t('ingredients.pleaseSelectACategory')" @change="getWork">
            <el-option
                v-for="item in options"
                :key="item.value"
                :label="item.label"
                :value="item.value"
            />
          </el-select>
        </template>
        <template #num1_filter="{ column, $panel }">
          <div>
            <div v-for="(option, index) in column.filters" :key="index">
              <input type="type" v-model="option.data" @keyup.enter.native="$panel.confirmFilter()" @input="changeFilterEvent($event, option, $panel)"/>
            </div>
          </div>
        </template>
        <template #button_slot="{ row }">
          <el-button @click="getTableRow(row,'add')"
                     link type="primary" size="small">{{ $t('basicData.add') }}</el-button>
        </template>
      </vxe-grid>
    </div>
@@ -157,7 +432,7 @@
  height: 100%;
}
.product{
  width: 35%;
  width: 30%;
  height:50%;
  float: left;
}
@@ -171,4 +446,23 @@
  height:50%;
  float: left;
}
.product-card { max-width: 960px; padding: 2px 2px 4px; }
.product-tabs { display: flex; flex-direction: column; gap: 2px 12px; }
.tab {
  margin: 0; padding: 6px 10px; line-height: 1.6;
  border-radius: 6px; cursor: pointer; user-select: none;
  transition: all .15s ease; border: 1px solid var(--el-border-color);
}
.tab:hover { transform: translateY(-1px); }
.tab.active {
  background: var(--el-color-primary-light-9);
  border-color: var(--el-color-primary);
  color: var(--el-color-primary); font-weight: 600;
}
.tab.active {
  font-weight: bold;
  color: #409EFF; /* Element Plus 主色 */
  border-bottom: 2px solid #409EFF;
}
</style>