spring
18 小时以前 eeefce8145b5ab47ba7148fc05092e2d01e1fc8e
fix: 搬迁军泰生产-可上传图片、下载流转码
已添加2个文件
已修改5个文件
456 ■■■■■ 文件已修改
src/api/productionManagement/productWorkOrderFile.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 147 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/components/filesDia.vue 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productWorkOrderFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
import request from "@/utils/request";
// æŸ¥è¯¢å·¥å•附件列表
export function productWorkOrderFileListPage(query) {
  return request({
    url: "/productWorkOrderFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žå·¥å•附件
export function productWorkOrderFileAdd(data) {
  return request({
    url: "/productWorkOrderFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤å·¥å•附件
export function productWorkOrderFileDel(data) {
  return request({
    url: "/productWorkOrderFile/del",
    method: "delete",
    data,
  });
}
src/api/productionManagement/workOrder.js
@@ -23,3 +23,13 @@
    data: data,
  });
}
// ä¸‹è½½å·¥å•流转卡(返回文件流)
export function downProductWorkOrder(id) {
  return request({
    url: "/productWorkOrder/down",
    method: "post",
    data: { id },
    responseType: "blob",
  });
}
src/views/productionManagement/productStructure/Detail/index.vue
@@ -2,85 +2,136 @@
  <div class="app-container">
    <PageHeader content="产品结构详情">
      <template #right-button>
        <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">编辑
        <el-button v-if="!dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="dataValue.isEdit = true">编辑
        </el-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">取消
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="cancelEdit">取消
        </el-button>
        <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit"
        <el-button v-if="!isOrderPage"
                   type="primary"
                   :loading="dataValue.loading"
                   @click="submit"
          :disabled="!dataValue.isEdit">确认
        </el-button>
      </template>
    </PageHeader>
    <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true"
    <el-table :data="tableData"
              border
              :preserve-expanded-content="false"
              :default-expand-all="true"
      style="width: 100%">
      <el-table-column type="expand">
        <template #default="props">
          <el-form ref="form" :model="dataValue">
            <el-table :data="dataValue.dataList" row-key="tempId" default-expand-all
              :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" style="width: 100%">
              <el-table-column prop="productName" label="产品" />
              <el-table-column prop="model" label="规格">
          <el-form ref="form"
                   :model="dataValue">
            <el-table :data="dataValue.dataList"
                      row-key="tempId"
                      default-expand-all
                      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
                      style="width: 100%">
              <el-table-column prop="productName"
                               label="产品" />
              <el-table-column prop="model"
                               label="规格">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请选择规格', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-select v-model="row.model" placeholder="请选择规格" clearable
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                      style="width: 100%" @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                      <el-option v-if="row.model" :label="row.model" :value="row.model" />
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                      <el-option v-if="row.model"
                                 :label="row.model"
                                 :value="row.model" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="processName" label="消耗工序">
              <el-table-column prop="processName"
                               label="消耗工序">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                    style="margin: 0">
                    <el-select v-model="row.processId" placeholder="请选择" filterable clearable style="width: 100%"
                    <el-select v-model="row.processId"
                               placeholder="请选择"
                               filterable
                               clearable
                               style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
                      <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name"
                      <el-option v-for="item in dataValue.processOptions"
                                 :key="item.id"
                                 :label="item.name"
                        :value="item.id" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unitQuantity" label="单位产出所需数量">
              <el-table-column prop="unitQuantity"
                               label="单位产出所需数量">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur', 'change'] }]"
                    style="margin: 0">
                    <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1"
                      controls-position="right" style="width: 100%"
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="需求总量">
              <el-table-column v-if="isOrderPage"
                               prop="demandedQuantity"
                               label="需求总量">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1"
                      controls-position="right" style="width: 100%"
                                :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unit" label="单位">
              <el-table-column prop="unit"
                               label="单位">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                    :rules="[{ required: true, message: '请输入单位', trigger: ['blur', 'change'] }]" style="margin: 0">
                    <el-input v-model="row.unit" placeholder="请输入单位" clearable
                                :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                      :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column label="操作" fixed="right" width="200">
              <el-table-column label="操作"
                               fixed="right"
                               width="200">
                <template #default="{ row, $index }">
                  <el-button
                    v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                    type="danger" text @click="removeItem(row.tempId)">删除
                  <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                             type="danger"
                             text
                             @click="removeItem(row.tempId)">删除
                  </el-button>
                  <el-button v-if="dataValue.isEdit" type="primary" text @click="addItem2(row.tempId)">添加
                  <el-button v-if="dataValue.isEdit"
                             type="primary"
                             text
                             @click="addItem2(row.tempId)">添加
                  </el-button>
                </template>
              </el-table-column>
@@ -88,11 +139,15 @@
          </el-form>
        </template>
      </el-table-column>
      <el-table-column label="BOM编号" prop="bomNo" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" />
      <el-table-column label="BOM编号"
                       prop="bomNo" />
      <el-table-column label="产品名称"
                       prop="productName" />
      <el-table-column label="规格型号"
                       prop="model" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog"
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
      @confirm="handleProduct" />
  </div>
</template>
@@ -287,11 +342,11 @@
      isValid = false;
      return;
    }
    if (!item.unit) {
      ElMessage.error("请输入单位");
      isValid = false;
      return;
    }
      // if (!item.unit) {
      //   ElMessage.error("请输入单位");
      //   isValid = false;
      //   return;
      // }
    // é€’归校验子项
    if (item.children && item.children.length > 0) {
@@ -425,6 +480,18 @@
  return false;
};
  const getPropPath = (row, field) => {
    // ä¸ºæ¯ä¸ªrow生成唯一的路径
    // ä½¿ç”¨row.id或索引作为唯一标识
    let path = "dataList";
    // ç®€å•实现:使用row的id或一个唯一标识
    const uniqueId = row.id || Math.floor(Math.random() * 10000);
    path += `.${uniqueId}`;
    return path + `.${field}`;
  };
const cancelEdit = () => {
  dataValue.isEdit = false;
  // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -111,6 +111,7 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
import useUserStore from "@/store/modules/user.js";
src/views/productionManagement/productionReporting/components/formDia.vue
@@ -94,6 +94,7 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
const { proxy } = getCurrentInstance()
src/views/productionManagement/workOrder/components/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
<template>
  <div>
    <el-dialog v-model="dialogVisible" title="工单附件" width="50%" @close="closeDia">
      <div style="margin-bottom: 10px; text-align: right">
        <el-upload
          v-model:file-list="fileList"
          class="upload-demo"
          :action="uploadUrl"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
          :before-upload="beforeUpload"
          name="file"
          :show-file-list="false"
          :headers="headers"
          accept="image/*"
          style="display: inline; margin-right: 10px"
        >
          <el-button type="primary">上传图片</el-button>
        </el-upload>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :total="page.total"
        :tableLoading="tableLoading"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        @pagination="paginationSearch"
        height="500"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">关闭</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
import { getToken } from "@/utils/auth.js";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import filePreview from "@/components/filePreview/index.vue";
import {
  productWorkOrderFileAdd,
  productWorkOrderFileDel,
  productWorkOrderFileListPage,
} from "@/api/productionManagement/productWorkOrderFile.js";
const { proxy } = getCurrentInstance();
const emit = defineEmits(["close"]);
const dialogVisible = ref(false);
const currentWorkOrderId = ref("");
const selectedRows = ref([]);
const filePreviewRef = ref();
const tableColumn = ref([
  {
    label: "文件名称",
    prop: "name",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    width: 120,
    operation: [
      {
        name: "下载",
        type: "text",
        clickFun: row => {
          proxy.$download.name(row.url);
        },
      },
      {
        name: "预览",
        type: "text",
        clickFun: row => {
          filePreviewRef.value?.open(row.url);
        },
      },
    ],
  },
]);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
const headers = ref({
  Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
const beforeUpload = file => {
  const isImage = file?.type?.startsWith("image/");
  if (!isImage) {
    proxy.$modal.msgError("只能上传图片文件");
  }
  return isImage;
};
const openDialog = row => {
  dialogVisible.value = true;
  currentWorkOrderId.value = row.id;
  page.current = 1;
  getList();
};
const closeDia = () => {
  dialogVisible.value = false;
  emit("close");
};
const paginationSearch = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  productWorkOrderFileListPage({
    workOrderId: currentWorkOrderId.value,
    current: page.current,
    size: page.size,
  })
    .then(res => {
      tableData.value = res.data.records || [];
      page.total = res.data.total || 0;
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
const handleSelectionChange = selection => {
  selectedRows.value = selection;
};
function handleUploadSuccess(res) {
  if (res.code == 200) {
    const fileRow = {
      name: res.data.originalName,
      url: res.data.tempPath,
      workOrderId: currentWorkOrderId.value,
    };
    productWorkOrderFileAdd(fileRow).then(() => {
      proxy.$modal.msgSuccess("文件上传成功");
      getList();
    });
  } else {
    proxy.$modal.msgError("文件上传失败");
  }
}
function handleUploadError() {
  proxy.$modal.msgError("文件上传失败");
}
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      productWorkOrderFileDel(ids).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
defineExpose({
  openDialog,
});
</script>
<style scoped></style>
src/views/productionManagement/workOrder/index.vue
@@ -209,6 +209,7 @@
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef" />
  </div>
</template>
@@ -220,10 +221,12 @@
    productWorkOrderPage,
    updateProductWorkOrder,
    addProductMain,
    downProductWorkOrder,
  } from "@/api/productionManagement/workOrder.js";
  import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
  import QRCode from "qrcode";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  import FilesDia from "./components/filesDia.vue";
  const { proxy } = getCurrentInstance();
  const tableColumn = ref([
@@ -307,7 +310,13 @@
        {
          name: "流转卡",
          clickFun: row => {
            showTransferCard(row);
            downloadAndPrintWorkOrder(row);
          },
        },
        {
          name: "附件",
          clickFun: row => {
            openWorkOrderFiles(row);
          },
        },
        {
@@ -330,6 +339,7 @@
  const transferCardQrUrl = ref("");
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const workOrderFilesRef = ref(null);
  const userOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
@@ -395,6 +405,56 @@
      });
  };
  // ä¸‹è½½å¹¶æ‰“印工单流转卡(文件流)
  const downloadAndPrintWorkOrder = async row => {
    if (!row || !row.id) {
      proxy.$modal.msgError("缺少工单ID,无法下载流转卡");
      return;
    }
    const fileName = row.workOrderNo
      ? `工单流转卡_${row.workOrderNo}.xlsx`
      : "工单流转卡.xlsx";
    try {
      // è°ƒç”¨æŽ¥å£ï¼Œä»¥ responseType: 'blob' èŽ·å–æ–‡ä»¶æµ
      const blob = await downProductWorkOrder(row.id);
      if (!blob) {
        proxy.$modal.msgError("未获取到流转卡文件");
        return;
      }
      // åˆ›å»º Blob URL
      const fileBlob =
        blob instanceof Blob ? blob : new Blob([blob], { type: blob.type || "application/octet-stream" });
      const url = window.URL.createObjectURL(fileBlob);
      // åˆ›å»ºéšè— iframe,用于触发浏览器打印
      const iframe = document.createElement("iframe");
      iframe.style.position = "fixed";
      iframe.style.right = "0";
      iframe.style.bottom = "0";
      iframe.style.width = "0";
      iframe.style.height = "0";
      iframe.style.border = "0";
      iframe.src = url;
      document.body.appendChild(iframe);
      iframe.onload = () => {
        try {
          iframe.contentWindow?.focus();
          iframe.contentWindow?.print();
        } catch (e) {
          console.error("自动调用打印失败", e);
          // é€€è€Œæ±‚其次,打开新窗口由用户手动打印
          window.open(url);
        }
      };
    } catch (e) {
      console.error("下载工单流转卡失败", e);
      proxy.$modal.msgError("下载工单流转卡失败");
    }
  };
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = String(row.id);
@@ -450,6 +510,10 @@
    reportDialogVisible.value = true;
  };
  const openWorkOrderFiles = row => {
    workOrderFilesRef.value?.openDialog(row);
  };
  const handleReport = () => {
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {