huminmin
2026-05-28 8ef070c84a703c4a8b838bf9320d68d00a7d6dca
src/views/productionManagement/productionOrder/BindRouteDialog.vue
@@ -62,7 +62,7 @@
        <el-table-column label="材料名称">
          <template #default="{ row, $index }">
            <el-tree-select
                v-if="!isDetail"
                v-if="!isDetail && !row.productById"
                v-model="row.productById"
                placeholder="请选择"
                clearable
@@ -70,7 +70,6 @@
                @change="(val) => getModels(val, row, $index)"
                :data="productOptions"
                :render-after-expand="false"
                :disabled="isDetail"
                style="width: 100%"
            />
            <span v-else>{{ row.name }}</span>
@@ -82,13 +81,13 @@
          </template>
          <template #default="{ row }">
            <el-select
                v-if="!isDetail"
                v-if="!isDetail && !row.productModelId"
                v-model="row.productModelId"
                placeholder="请选择规格"
                filterable
                clearable
                @change="(val) => handleMaterialModelChange(val, row)"
                :disabled="isDetail"
                style="width: 100%"
            >
              <el-option
                  v-for="item in row.modelOptions"
@@ -107,8 +106,9 @@
          </template>
        </el-table-column>
        <el-table-column label="计量单位">
          <template #default="{ row }">
            <el-input v-model="row.unit" placeholder="计量单位" :disabled="isDetail"/>
          <template  #default="{ row }">
            <el-input  v-if="props.type === 'add'" v-model="row.unit" placeholder="计量单位" :disabled="isDetail"/>
            <span v-else>{{ row.unit }}</span>
          </template>
        </el-table-column>
        <el-table-column label="单价">
@@ -135,7 +135,7 @@
            align="center"
            style="white-space: pre-line; word-break: break-all; min-height: 60px;"
        >
          <el-radio-group v-model="formData.cuttingDiagramCheckout">
          <el-radio-group v-model="formData.cuttingDiagramCheckout" :disabled="isDetail">
            <el-radio value="平张">平张</el-radio>
            <el-radio value="卷筒">卷筒</el-radio>
          </el-radio-group>
@@ -219,7 +219,7 @@
            <th colspan="2">开张色</th>
            <th>晒板</th>
            <th colspan="2">开拼</th>
            <th>别刀版</th>
            <th>刖刀版</th>
            <th>联色块</th>
          </tr>
          <tr>
@@ -228,7 +228,7 @@
            <th>拼版费</th>
            <th>出片费</th>
            <th>打样费</th>
            <th>别刀版费</th>
            <th>刖刀版费</th>
            <th>烫/凸版费</th>
            <th>小计</th>
          </tr>
@@ -246,7 +246,7 @@
              <el-input v-model="plate.proofingFee" placeholder="请输入打样费" :disabled="isDetail"/>
            </td>
            <td>
              <el-input v-model="plate.doctorBladePlateFee" placeholder="请输入别刀版费" :disabled="isDetail"/>
              <el-input v-model="plate.doctorBladePlateFee" placeholder="请输入刖刀版费" :disabled="isDetail"/>
            </td>
            <td>
              <el-input v-model="plate.hotEmbossingPlateFee" placeholder="请输入烫/凸版费" :disabled="isDetail"/>
@@ -373,27 +373,27 @@
      <!-- ================= 包装信息 ================= -->
      <el-descriptions border :column="3" class="mt">
        <el-descriptions-item label="送货地点" align="center">
        <el-descriptions-item label="送货地点">
          <el-input v-model="formData.deliveryAddress" placeholder="送货地点" :disabled="isDetail"/>
        </el-descriptions-item>
        <el-descriptions-item label="联系人" align="center">
        <el-descriptions-item label="联系人">
          <el-input v-model="formData.contactName" placeholder="联系人" :disabled="isDetail"/>
        </el-descriptions-item>
        <el-descriptions-item label="包装要求" align="center">
        <el-descriptions-item label="包装要求">
          <el-input v-model="formData.packagingRequirement" placeholder="包装要求" :disabled="isDetail"/>
        </el-descriptions-item>
        <el-descriptions-item label="尺寸" align="center">
        <el-descriptions-item label="尺寸">
          <el-input v-model="formData.postProcessSize" placeholder="尺寸" :disabled="isDetail"/>
        </el-descriptions-item>
        <el-descriptions-item label="定货数量" align="center">
        <el-descriptions-item label="定货数量">
          {{ formData.orderQty || "--" }}
        </el-descriptions-item>
        <el-descriptions-item label="实交数量" :span="3" align="center">
        <el-descriptions-item label="实交数量" :span="3">
          <el-input v-model="formData.actualDeliveryQty" placeholder="实交数量" :disabled="isDetail"/>
        </el-descriptions-item>
      </el-descriptions>
@@ -460,6 +460,7 @@
const filePreviewRef = ref()
const formData = reactive({
  id: null,
  productOrderList: null,
  salesLedgerId: null,
  productOrderId: null,
@@ -513,6 +514,8 @@
  ],
  materialInfo: [
    {
      parentId: "",
      productById: "",
      productId: "",
      name: "",
      productModelId: "",
@@ -561,6 +564,7 @@
const cloneDeep = (val) => JSON.parse(JSON.stringify(val))
const createDefaultFormData = () => ({
  id: null,
  productOrderList: null,
  salesLedgerId: null,
  productOrderId: null,
@@ -614,6 +618,8 @@
  ],
  materialInfo: [
    {
      parentId: "",
      productById: "",
      productId: "",
      name: "",
      productModelId: "",
@@ -702,6 +708,7 @@
  }
  Object.keys(formData).forEach((key) => {
    if (key === 'id') return
    if (source[key] !== undefined) {
      formData[key] = Array.isArray(source[key]) ? cloneDeep(source[key]) : source[key]
    }
@@ -720,6 +727,20 @@
        }))
  }
  if (Array.isArray(formData.materialInfo)) {
    formData.materialInfo = formData.materialInfo.map((m) => {
      const parentId = m?.parentId ? String(m.parentId) : ""
      const productById = m?.productById ? String(m.productById) : ""
      const name = m?.name ?? ""
      return {
        ...m,
        parentId,
        productById: productById || parentId || "",
        name: name || (parentId ? findProductLabelById(productOptions.value, parentId) : "")
      }
    })
  }
  // 兼容 index.vue 里常用字段名与弹窗字段名不一致的情况
  if (source.productName === undefined && source.productCategory !== undefined) {
    formData.productName = source.productCategory
@@ -735,6 +756,16 @@
  }
  if (source.productOrderId === undefined && source.id !== undefined) {
    formData.productOrderId = source.id
  }
  // 编辑/查看时需要把 productionProductInput 的 id 带回去(后端 save 走更新)
  // 列表行里一般是 printId;详情接口一般返回 { id, productOrderId, ... }
  if (source.printId !== undefined && source.printId !== null && source.printId !== '') {
    formData.id = source.printId
  } else if (source.productionProductInputId !== undefined && source.productionProductInputId !== null && source.productionProductInputId !== '') {
    formData.id = source.productionProductInputId
  } else if (source.productOrderId !== undefined && source.id !== undefined && source.id !== null && source.id !== '') {
    formData.id = source.id
  }
  introductionLetterList.value = String(formData.introductionLetter || "")
@@ -766,6 +797,7 @@
    (val) => {
      mergeRowDataToForm(val)
      getProductOrder()
      Promise.resolve().then(() => hydrateMaterialInfo())
    },
    {immediate: true, deep: true}
)
@@ -792,14 +824,14 @@
const convertProductOptions = (data) => {
  return data.map(item => ({
    label: item.label || item.productName || item.name || "",
    value: item.id,
    value: String(item.id ?? ""),
    children: item.children?.length ? convertProductOptions(item.children) : undefined
  }))
}
const findProductLabelById = (options, productId) => {
  for (const item of options) {
    if (item.value === productId) {
    if (String(item.value) === String(productId)) {
      return item.label
    }
    if (item.children?.length) {
@@ -812,10 +844,69 @@
  return ""
}
const normalizeId = (v) => {
  if (v === 0 || v === "0") return "0"
  if (v === null || v === undefined) return ""
  const s = String(v)
  return s === "null" || s === "undefined" ? "" : s
}
const materialModelOptionsCache = new Map()
const normalizeModelOptions = (res) => {
  return Array.isArray(res) ? res :
      Array.isArray(res?.data) ? res.data :
          Array.isArray(res?.rows) ? res.rows :
              Array.isArray(res?.data?.records) ? res.data.records :
                  []
}
const getModelOptionsByParentId = async (parentId) => {
  const key = normalizeId(parentId)
  if (!key) return []
  if (materialModelOptionsCache.has(key)) return materialModelOptionsCache.get(key)
  const res = await modelList({id: key})
  const options = normalizeModelOptions(res)
  materialModelOptionsCache.set(key, options)
  return options
}
const hydrateMaterialInfo = async () => {
  const rows = Array.isArray(formData.materialInfo) ? formData.materialInfo : []
  await Promise.all(rows.map(async (row) => {
    if (!row || typeof row !== 'object') return
    const parentId = normalizeId(row.parentId || row.productById)
    if (!parentId) return
    row.parentId = parentId
    row.productById = parentId
    if (!row.name) {
      row.name = findProductLabelById(productOptions.value, parentId)
    }
    if (!Array.isArray(row.modelOptions) || row.modelOptions.length === 0) {
      row.modelOptions = await getModelOptionsByParentId(parentId)
    }
    const productModelId = normalizeId(row.productModelId)
    if (productModelId && Array.isArray(row.modelOptions) && row.modelOptions.length) {
      const currentModel = row.modelOptions.find(item => normalizeId(item?.id) === productModelId)
      if (currentModel) {
        row.model = currentModel?.model || row.model || ""
        row.unit = currentModel?.unit || row.unit || ""
        if (!row.productId) {
          row.productId = currentModel?.id || ""
        }
      }
    }
  }))
}
const getMaterialProductOptions = () => {
  productTreeList().then(res => {
    const rawData = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []
    productOptions.value = convertProductOptions(rawData)
    Promise.resolve().then(() => hydrateMaterialInfo())
  })
}
@@ -847,26 +938,31 @@
}
const getModels = async (val, row, index) => {
  const targetRow = formData.materialInfo[index]
  row.productId = val || ""
  row.productById = val || ""
  row.name = val ? findProductLabelById(productOptions.value, val) : ""
  const targetRow = formData.materialInfo[index] || {}
  const parentId = val ? String(val) : ""
  const name = parentId ? findProductLabelById(productOptions.value, parentId) : ""
  row.productModelId = ""
  row.model = ""
  row.unit = ""
  row.modelOptions = []
  if (!val) return
  const res = await modelList({id: val})
  formData.materialInfo[index] = {
  const baseRow = {
    ...targetRow,
    modelOptions: Array.isArray(res) ? res :
        Array.isArray(res?.data) ? res.data :
            Array.isArray(res?.rows) ? res.rows :
                Array.isArray(res?.data?.records) ? res.data.records :
                    []
    parentId,
    productId: "",
    productById: parentId,
    name,
    productModelId: "",
    model: "",
    unit: "",
    modelOptions: []
  }
  formData.materialInfo[index] = baseRow
  if (!parentId) return
  const modelOptions = await getModelOptionsByParentId(parentId)
  formData.materialInfo[index] = {
    ...baseRow,
    modelOptions
  }
  // row.modelOptions = Array.isArray(res) ? res :
  //     Array.isArray(res?.data) ? res.data :
@@ -888,6 +984,8 @@
const addMaterialRow = () => {
  formData.materialInfo.push({
    id: Date.now().toString(),
    parentId: "",
    productById: "",
    productId: "",
    name: "",
    productModelId: "",
@@ -956,24 +1054,24 @@
  const materialRows = Array.isArray(formData.materialInfo) ? formData.materialInfo : []
  for (let i = 0; i < materialRows.length; i++) {
    const row = materialRows[i] || {}
    if (!row.productId) {
    if (!row.productModelId) {
      ElMessage.warning(`材料信息第${i + 1}行:   规格必填`)
      return
    }
  }
  const rows = Array.isArray(formData.processContent) ? formData.processContent : []
  for (let i = 0; i < rows.length; i++) {
    const row = rows[i] || {}
    if (!row.deviceId) {
      ElMessage.warning(`工艺加工第${i + 1}行:机台必填`)
      return
    }
    if (!Array.isArray(row.reportUserIds) || row.reportUserIds.length === 0) {
      ElMessage.warning(`工艺加工第${i + 1}行:报工人必填`)
      return
    }
  }
  // for (let i = 0; i < rows.length; i++) {
  //   const row = rows[i] || {}
  //   if (!row.deviceId) {
  //     ElMessage.warning(`工艺加工第${i + 1}行:机台必填`)
  //     return
  //   }
  //   if (!Array.isArray(row.reportUserIds) || row.reportUserIds.length === 0) {
  //     ElMessage.warning(`工艺加工第${i + 1}行:报工人必填`)
  //     return
  //   }
  // }
  const payload = cloneDeep(formData)
  delete payload.productOrderList
  if (Array.isArray(payload.materialInfo)) {
@@ -1123,4 +1221,7 @@
    -webkit-box-orient: vertical;
  }
}
.mx-1{
  text-align: center;
}
</style>