张诺
7 小时以前 5cfebd7e46c0c53f79b5fb4a917e926194ab4398
src/views/productionManagement/productionOrder/BindRouteDialog.vue
@@ -1,7 +1,7 @@
<template>
  <FormDialog
    v-model="visible"
    :title="type === 'add' ? '绑定工艺路线' : '编辑工艺路线'"
    :title="type === 'add' ? '绑定工艺路线' : type === 'detail' ? '查看工艺路线' : '编辑工艺路线'"
    width="1400px"
    :operation-type="type"
    :column="8"
@@ -9,6 +9,7 @@
    @confirm="handleConfirm"
    @cancel="handleClose"
  >
    <div :class="{ 'is-detail': isDetail }">
    <!-- ================= 基本信息 ================= -->
    <el-descriptions :column="3">
      <el-descriptions-item label="编号" align="center" v-if="formData.productOrderList">
@@ -34,7 +35,7 @@
      </el-descriptions-item>
      <el-descriptions-item label="成品尺寸" :span="1" align="center">
        {{formData.specificationModel || "--"}}
        {{formData.finishedSize || "--"}}
      </el-descriptions-item>
      <el-descriptions-item label="产品名称" :span="2" align="center">
@@ -42,7 +43,7 @@
      </el-descriptions-item>
      <el-descriptions-item label="单据类型" :span="2" align="center">
        <el-checkbox-group v-model="introductionLetterList">
        <el-checkbox-group v-model="introductionLetterList" :disabled="isDetail">
          <el-checkbox label="介绍信" value="介绍信" />
          <el-checkbox label="商标注册书" value="商标注册书" />
          <el-checkbox label="委印单" value="委印单" />
@@ -53,12 +54,13 @@
    <!-- ================= 材料表 ================= -->
    <div class="process-table-header">
     <div class="section-title">材料信息</div>
      <el-button type="primary" size="small" @click="addMaterialRow">新增一行</el-button>
      <el-button v-if="!isDetail" type="primary" size="small" @click="addMaterialRow">新增一行</el-button>
    </div>
    <el-table border :data="formData.materialInfo" style="width: 100%">
      <el-table-column label="材料名称">
        <template #default="{ row }">
        <template #default="{ row }" >
          <el-tree-select
          v-if="!isDetail"
            v-model="row.productId"
            placeholder="请选择"
            clearable
@@ -66,18 +68,22 @@
            @change="(val) => getModels(val, row)"
            :data="productOptions"
            :render-after-expand="false"
            :disabled="isDetail"
            style="width: 100%"
          />
        </template>
            <span v-else>{{ row.name }}</span>
        </template>
      </el-table-column>
      <el-table-column label="规格">
        <template #default="{ row }">
          <el-select
          v-if="!isDetail"
            v-model="row.productModelId"
            placeholder="请选择规格"
            filterable
            clearable
            @change="(val) => handleMaterialModelChange(val, row)"
            :disabled="isDetail"
          >
            <el-option
              v-for="item in row.modelOptions || []"
@@ -86,33 +92,32 @@
              :value="item.id"
            />
          </el-select>
          <span v-else>{{ row.model }}</span>
        </template>
      </el-table-column>
      <el-table-column label="数量">
        <template #default="{ row }">
          <el-input v-model="row.num" placeholder="数量">
            <template #append>{{ row.numSuffix }}</template>
          <el-input v-model="row.num" placeholder="数量" :disabled="isDetail">
          </el-input>
        </template>
      </el-table-column>
      <el-table-column label="计量单位">
        <template #default="{ row }">
          <el-input v-model="row.unit" placeholder="计量单位" />
          <el-input v-model="row.unit" placeholder="计量单位" :disabled="isDetail" />
        </template>
      </el-table-column>
      <el-table-column label="单价">
        <template #default="{ row }">
          <el-input v-model="row.price" placeholder="单价">
            <template #append>{{ row.unitSuffix }}</template>
          <el-input v-model="row.price" placeholder="单价" :disabled="isDetail">
          </el-input>
        </template>
      </el-table-column>
      <el-table-column label="金额">
        <template #default="{ row }">
          <el-input v-model="row.totalAmount" placeholder="金额" />
          <el-input v-model="row.totalAmount" placeholder="金额" :disabled="isDetail" />
        </template>
      </el-table-column>
      <el-table-column label="操作" width="80">
      <el-table-column v-if="!isDetail" label="操作" width="80">
        <template #default="{ $index }">
          <el-button type="danger" size="small" @click="removeMaterialRow($index)">删除</el-button>
        </template>
@@ -130,20 +135,34 @@
            :autosize="{ minRows: 2, maxRows: 4 }"
            type="textarea"
            placeholder="请输入注意事项"
            :disabled="isDetail"
        />
      </el-descriptions-item>
    </el-descriptions>
    <hr>
    <!-- ================= 切料图示 ================= -->
    <div class="section-title">切料图示</div>
    <ActionFileUpload
        style="width: 50%;"
        v-model:file-list="fileList"
        style="width: 50%;float: left;"
        v-model:file-list="formData.cuttingFileVo"
        :action="upload.url"
        :headers="upload.headers"
        :multiple="false"
        :limit="1"
        :replaceOnExceed="true"
        :disabled="isDetail"
        :name="'files'"
    tip-text="支持图片(jpg, jpeg, png)格式"
        :onSuccess="onSuccess"
        :onDownload="onDownload"
        :onRemove="onRemove"
        :onPreview="onPreview"
        :onView="type === 'detail' ? false : true"
        :tip-text="type === 'detail' ? '' : '支持图片(jpg, jpeg, png)格式'"
    />
    <el-image
      v-if="formData.cuttingFileVo.length > 0"
      style="width: 100px; height: 100px"
      :src="resolveFileUrl(getUploadFileUrl(formData.cuttingFileVo[0]))"
      fit="cover"
    />
    <!-- ================= 切料信息 ================= -->
    <el-descriptions
@@ -154,22 +173,22 @@
      class="fixed-desc"
    >
      <el-descriptions-item label="切料尺寸" align="center">
        <el-input v-model="formData.cutNum" placeholder="切料尺寸" />
        <el-input v-model="formData.cutNum" placeholder="切料尺寸" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="切料数量" align="center">
        <el-input v-model="formData.cutSize" placeholder="切料尺寸" />
        <el-input v-model="formData.cutSize" placeholder="切料尺寸" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="中盒数量" align="center">
        <el-input v-model="formData.mediumBoxQty" placeholder="中盒数量" />
        <el-input v-model="formData.mediumBoxQty" placeholder="中盒数量" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="小盒数量" align="center">
        <el-input v-model="formData.smallBoxQty" placeholder="小盒数量" />
        <el-input v-model="formData.smallBoxQty" placeholder="小盒数量" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="正数" align="center">
        <el-input v-model="formData.positiveQty" placeholder="正数" />
        <el-input v-model="formData.positiveQty" placeholder="正数" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="加放数" align="center">
        <el-input v-model="formData.allowanceQty" placeholder="加放数" />
        <el-input v-model="formData.allowanceQty" placeholder="加放数" :disabled="isDetail" />
      </el-descriptions-item>
    </el-descriptions>
@@ -196,25 +215,25 @@
          </tr>
          <tr v-for="(plate, index) in formData.plateMaking" :key="index">
            <td>
              <el-input v-model="plate.designProductionFee" placeholder="请输入设计制作费" />
              <el-input v-model="plate.designProductionFee" placeholder="请输入设计制作费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.impositionFee" placeholder="请输入拼版费" />
              <el-input v-model="plate.impositionFee" placeholder="请输入拼版费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.filmOutputFee" placeholder="请输入出片费" />
              <el-input v-model="plate.filmOutputFee" placeholder="请输入出片费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.proofingFee" placeholder="请输入打样费" />
              <el-input v-model="plate.proofingFee" placeholder="请输入打样费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.doctorBladePlateFee" placeholder="请输入别刀版费" />
              <el-input v-model="plate.doctorBladePlateFee" placeholder="请输入别刀版费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.hotEmbossingPlateFee" placeholder="请输入烫/凸版费" />
              <el-input v-model="plate.hotEmbossingPlateFee" placeholder="请输入烫/凸版费" :disabled="isDetail" />
            </td>
            <td>
              <el-input v-model="plate.subtotalFee" placeholder="请输入小计" />
              <el-input v-model="plate.subtotalFee" placeholder="请输入小计" :disabled="isDetail" />
            </td>
          </tr>
        </tbody>
@@ -224,17 +243,16 @@
    <!-- ================= 工艺加工 ================= -->
    <div class="process-table-header">
     <div class="section-title">工艺加工</div>
      <el-button type="primary" size="small" @click="addProcessRow">新增一行</el-button>
      <el-button v-if="!isDetail" type="primary" size="small" @click="addProcessRow">新增一行</el-button>
    </div>
    <el-table border :data="formData.processContent" style="width: 100%" :span-method="objectSpanMethod">
      <el-table-column label="工序" width="140">
        <template #default="{ row }">
          <el-table-column label="工序" width="140">
            <template #default="{ row }">
              <el-select
                  v-model="row.processId"
                  placeholder="请选择工序"
                  @change="(val) => onProcessChange(val, row)"
                  :disabled="isDetail"
              >
                <el-option
                    v-for="item in processOptions"
@@ -245,24 +263,25 @@
              </el-select>
            </template>
          </el-table-column>
        </template>
      </el-table-column>
      <el-table-column label="开数">
        <template #default="{ row }">
          <el-input v-model="row.openCount" placeholder="请输入开数" />
          <el-input v-model="row.openCount" placeholder="请输入开数" :disabled="isDetail" />
        </template>
      </el-table-column>
      <el-table-column label="工艺正数">
        <template #default="{ row }">
          <el-input v-model="row.processPositive" placeholder="请输入工艺正数" />
          <el-input v-model="row.processPositive" placeholder="请输入工艺正数" :disabled="isDetail" />
        </template>
      </el-table-column>
      <el-table-column label="加放数">
        <template #default="{ row }">
          <el-input v-model="row.allowanceQty" placeholder="请输入加放数" />
          <el-input v-model="row.allowanceQty" placeholder="请输入加放数" :disabled="isDetail" />
        </template>
      </el-table-column>
      <el-table-column label="机台" width="180">
      <el-table-column width="180">
        <template #header>
          <span class="required">*</span>机台
        </template>
        <template #default="{ row }">
          <el-select
            v-model="row.deviceId"
@@ -270,6 +289,7 @@
            filterable
            clearable
            @change="(val) => handleDeviceChange(val, row)"
            :disabled="isDetail"
          >
            <el-option
              v-for="item in deviceOptions"
@@ -280,9 +300,13 @@
          </el-select>
        </template>
      </el-table-column>
      <el-table-column label="报工人" width="220">
      <el-table-column width="220">
        <template #header>
          <span class="required">*</span>报工人
        </template>
        <template #default="{ row }">
          <el-select
          v-if="!isDetail"
            v-model="row.reportUserIds"
            placeholder="请选择报工人"
            filterable
@@ -291,6 +315,7 @@
            collapse-tags
            collapse-tags-tooltip
            @change="(val) => handleReportUsersChange(val, row)"
            :disabled="isDetail"
          >
            <el-option
              v-for="item in userOptions"
@@ -299,6 +324,14 @@
              :value="item.userId"
            />
          </el-select>
  <el-tag
  v-else
    v-for="item in row.reportWorkerList"
    :key="item.id"
  >
    {{ item.userName }}
  </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="工艺要求">
@@ -308,10 +341,11 @@
            type="textarea"
            :rows="6"
            placeholder="请输入工艺要求"
            :disabled="isDetail"
          />
        </template>
      </el-table-column>
      <el-table-column label="操作" width="80">
      <el-table-column v-if="!isDetail" label="操作" width="80">
        <template #default="{ $index }">
          <el-button type="danger" size="small" @click="removeProcessRow($index)">删除</el-button>
        </template>
@@ -321,19 +355,19 @@
    <!-- ================= 包装信息 ================= -->
    <el-descriptions border :column="3" class="mt">
      <el-descriptions-item label="送货地点" align="center">
        <el-input v-model="formData.deliveryAddress" placeholder="送货地点" />
        <el-input v-model="formData.deliveryAddress" placeholder="送货地点" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="联系人" align="center">
        <el-input v-model="formData.contactName" placeholder="联系人" />
        <el-input v-model="formData.contactName" placeholder="联系人" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="包装要求" align="center">
        <el-input v-model="formData.packagingRequirement" placeholder="包装要求" />
        <el-input v-model="formData.packagingRequirement" placeholder="包装要求" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="尺寸" align="center">
        <el-input v-model="formData.postProcessSize" placeholder="尺寸" />
        <el-input v-model="formData.postProcessSize" placeholder="尺寸" :disabled="isDetail" />
      </el-descriptions-item>
      <el-descriptions-item label="定货数量" align="center">
@@ -341,14 +375,16 @@
      </el-descriptions-item>
      <el-descriptions-item label="实交数量" :span="3" align="center">
        <el-input v-model="formData.actualDeliveryQty" placeholder="实交数量" />
        <el-input v-model="formData.actualDeliveryQty" placeholder="实交数量" :disabled="isDetail" />
      </el-descriptions-item>
    </el-descriptions>
    </div>
  </FormDialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { ref, reactive, computed, onMounted, watch, getCurrentInstance } from 'vue'
import dayjs from 'dayjs'
import FormDialog from '@/components/Dialog/FormDialog.vue'
import ActionFileUpload from "@/components/Upload/ActionFileUpload.vue";
@@ -358,6 +394,8 @@
import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"
import { userListNoPageByTenantId } from "@/api/system/user.js"
import { getToken } from "@/utils/auth";
import filePreview from '@/components/filePreview/index.vue'
import { ElMessage } from "element-plus";
const props = defineProps({
  modelValue: {
@@ -379,30 +417,31 @@
})
const emit = defineEmits(['update:modelValue', 'confirm'])
const { proxy } = getCurrentInstance()
const visible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const isDetail = computed(() => props.type === 'detail')
const processOptions = ref([])
const deviceOptions = ref([])
const userOptions = ref([])
const reportWorkerList = ref([])
const productOptions = ref([])
const introductionLetterList = ref([])
const fileList = ref([])
const upload = reactive({
  url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload',
  headers: { Authorization: 'Bearer ' + getToken() }
})
const filePreviewRef = ref()
const formData = reactive({
  productOrderList:null,
  salesLedgerId: null,
  productOrderId: null,
  printOrderTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
  fileList:[],
  cuttingFileVo:[],
  finishTime: "",
  no: "",
  productName: "",
@@ -473,16 +512,158 @@
  specificationModel:"",
})
const getUploadFileUrl = (file) => {
  console.log("file", file)
  const response = file?.response
  const data = response?.data
  if (Array.isArray(data) && data.length) {
    return data[0]?.fileUrl || data[0]?.url || data[0]?.tempPath || ""
  }
  return file?.url || file?.fileUrl || data?.tempPath || data?.url || data?.fileUrl || ""
}
// 监听 checkbox group 变化并同步到 introductionLetter 字符串
watch(introductionLetterList, (val) => {
  formData.introductionLetter = val.join(',')
})
const onProcessChange = (processId, row) => {
  const selected = processOptions.find(item => item.id === processId)
  const selected = processOptions.value.find(item => item.id === processId)
  row.processName = selected?.name || ''
}
const cloneDeep = (val) => JSON.parse(JSON.stringify(val))
const createDefaultFormData = () => ({
  productOrderList: null,
  salesLedgerId: null,
  productOrderId: null,
  printOrderTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
  cuttingFileVo: [],
  finishTime: "",
  no: "",
  productName: "",
  productDescription: "",
  clientName: "",
  finishedSize: "",
  cutNum: "",
  cutSize: "",
  mediumBoxQty: "",
  smallBoxQty: "",
  positiveQty: "",
  allowanceQty: "",
  introductionLetter: "",
  plateMaking: [
    {
      designProductionFee: "",
      impositionFee: "",
      filmOutputFee: "",
      proofingFee: "",
      doctorBladePlateFee: "",
      hotEmbossingPlateFee: "",
      subtotalFee: ""
    }
  ],
  processContent: [
    {
      id: "1",
      processId: "",
      processName: "",
      mediumBoxQty: "",
      smallBoxQty: "",
      openCount: "",
      processPositive: "",
      allowanceQty: "",
      deviceId: "",
      deviceName: "",
      reportUserIds: [],
      reportWorkerList: []
    }
  ],
  materialInfo: [
    {
      id: "1",
      productId: "",
      name: "",
      productModelId: "",
      model: "",
      modelOptions: [],
      num: "",
      numSuffix: "张",
      unitSuffix: "元/kg",
      unit: "",
      price: "",
      totalAmount: ""
    }
  ],
  processRequirement: "",
  deliveryAddress: "",
  contactName: "",
  packagingRequirement: "",
  postProcessSize: "",
  orderQty: "",
  actualDeliveryQty: "",
  productionDept: "",
  technicalDept: "",
  warehouseDept: "",
  productModelId: "",
  specificationModel: "",
  cuttingFileId:""
})
const resetForm = () => {
  const next = createDefaultFormData()
  Object.keys(next).forEach((key) => {
    formData[key] = Array.isArray(next[key]) ? cloneDeep(next[key]) : next[key]
  })
  introductionLetterList.value = []
}
const onSuccess = (response, uploadFile, uploadFiles) => {
    const data = response?.data
  if (uploadFile && !uploadFile.url) {
    uploadFile.url = (Array.isArray(data) ? data?.[0]?.fileUrl : data?.fileUrl) || data?.tempPath || data?.url || response?.url || ""
  }
  if (Array.isArray(uploadFiles) && uploadFiles.length) {
    formData.cuttingFileVo = [uploadFiles[uploadFiles.length - 1]]
    formData.cuttingFileId = data?.[0]?.id || ""
  }
}
const JavaApi = computed(() => proxy?.javaApi || "")
const onRemove = (file) => {
  formData.cuttingFileVo = []
  formData.cuttingFileId = ""
}
const resolveFileUrl = (rawUrl) => {
  console.log("rawUrl", rawUrl)
  const u = String(rawUrl || "")
  if (!u) return ""
  if (/^(https?:)?\/\//i.test(u)) return u
  if (/^(blob:|data:)/i.test(u)) return u
  const base = String(JavaApi.value || "").replace(/\/+$/, "")
  if (!base) return u
  if (u.startsWith("/")) return base + u
  return base + "/" + u
}
// 文件预览/下载
const onDownload = (file) => {
  console.log(file)
  const url = resolveFileUrl(getUploadFileUrl(file))
  if (!url) return
  proxy?.$modal?.loading?.("正在下载文件,请稍候...")
  proxy.$download.name(url);
  proxy?.$modal?.closeLoading?.()
}
const onPreview = (file) => {
  const url = resolveFileUrl(getUploadFileUrl(file))
  if (!url) return
  filePreviewRef.value?.openUrl?.(url);
}
const mergeRowDataToForm = (source) => {
@@ -495,6 +676,19 @@
      formData[key] = Array.isArray(source[key]) ? cloneDeep(source[key]) : source[key]
    }
  })
  if (formData.cuttingFileVo && !Array.isArray(formData.cuttingFileVo)) {
    formData.cuttingFileVo = [formData.cuttingFileVo]
  }
  if (Array.isArray(formData.cuttingFileVo)) {
    formData.cuttingFileVo = formData.cuttingFileVo
      .filter(Boolean)
      .map((f) => ({
        ...f,
        name: f?.name || f?.fileName || "",
        url: f?.url || f?.fileUrl || "",
      }))
  }
  // 兼容 index.vue 里常用字段名与弹窗字段名不一致的情况
  if (source.productName === undefined && source.productCategory !== undefined) {
@@ -512,6 +706,11 @@
  if (source.productOrderId === undefined && source.id !== undefined) {
    formData.productOrderId = source.id
  }
  introductionLetterList.value = String(formData.introductionLetter || "")
    .split(",")
    .map(s => s.trim())
    .filter(Boolean)
}
// 获取销售订单
@@ -704,7 +903,30 @@
}
const handleConfirm = () => {
  emit('confirm', JSON.parse(JSON.stringify(formData)))
  if (isDetail.value) {
    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
    }
  }
  const payload = cloneDeep(formData)
  delete payload.productOrderList
  if (Array.isArray(payload.materialInfo)) {
    payload.materialInfo = payload.materialInfo.map((item) => {
      const { modelOptions, ...rest } = item || {}
      return rest
    })
  }
  emit('confirm', payload)
}
onMounted(() => {
@@ -714,7 +936,8 @@
  getMaterialProductOptions()
})
defineExpose({
  getProductOrder
  getProductOrder,
  resetForm
})
</script>
@@ -784,7 +1007,59 @@
.mt {
  margin-top: 20px;
}
:deep(.required) {
  color: #f56c6c;
}
:deep(.el-textarea__inner){
  box-shadow: none;
}
.is-detail {
  :deep(.el-input__wrapper) {
    box-shadow: none !important;
    background-color: transparent !important;
  }
  :deep(.el-input__inner) {
    color: var(--el-text-color-regular) !important;
    -webkit-text-fill-color: var(--el-text-color-regular) !important;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  :deep(.el-input-group__append),
  :deep(.el-input-group__prepend) {
    background-color: transparent !important;
    box-shadow: none !important;
    color: var(--el-text-color-regular) !important;
  }
  :deep(.el-select__wrapper) {
    box-shadow: none !important;
    background-color: transparent !important;
  }
  :deep(.el-select__selected-item) {
    color: var(--el-text-color-regular) !important;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  :deep(.el-select__caret) {
    display: none;
  }
  :deep(.el-textarea__inner) {
    box-shadow: none !important;
    background-color: transparent !important;
    color: var(--el-text-color-regular) !important;
    -webkit-text-fill-color: var(--el-text-color-regular) !important;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
}
</style>