yuan
2026-04-24 23ad2619d81e9e45a7fb4715b4b850e94bd843e7
Merge remote-tracking branch 'origin/dev_NEW_pro' into dev_NEW_pro
已修改15个文件
1122 ■■■■ 文件已修改
src/api/productionManagement/processRoute.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AttachmentPreview/image/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AttachmentUpload/image/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ProcessParamListDialog.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue 517 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js
@@ -12,7 +12,7 @@
export function add(data) {
  return request({
    url: "/technologyRouting",
    url: "/technologyRouting/addTechRoute",
    method: "post",
    data: data,
  });
@@ -34,7 +34,7 @@
export function update(data) {
  return request({
    url: "/technologyRouting",
    url: "/technologyRouting/editTechRoute",
    method: "put",
    data: data,
  });
src/api/productionManagement/processRouteItem.js
@@ -82,3 +82,11 @@
    data: data,
  });
}
// 按工艺路线工序同步工序参数-生产订单
export function syncProcessParamItemOrder(data) {
  return request({
    url: "/productionOrderRoutingOperationParam/sync",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productProcessRoute.js
@@ -4,7 +4,7 @@
// 列表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: "/productProcessRoute/list",
    url: "/productionOrderRouting/list",
    method: "get",
    params: query,
  });
@@ -12,7 +12,7 @@
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/productProcessRoute/updateRouteItem",
    url: "/productionOrderRouting/updateRouteItem",
    method: "post",
    data: data,
  });
@@ -21,7 +21,7 @@
// 生产订单下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/productProcessRoute/addRouteItem",
    url: "/productionOrderRouting/addRouteItem",
    method: "post",
    data,
  });
@@ -30,7 +30,7 @@
// 获取生产订单关联的工艺路线主信息
export function listMain(orderId) {
  return request({
    url: "/productProcessRoute/listMain",
    url: "/productionOrderRouting/listMain",
    method: "get",
    params: { orderId },
  });
@@ -39,7 +39,7 @@
// 删除工艺路线项目(路由后拼接 id)
export function deleteRouteItem(id) {
  return request({
    url: `/productProcessRoute/deleteRouteItem/${id}`,
    url: `/productionOrderRouting/deleteRouteItem/${id}`,
    method: "delete",
  });
}
@@ -47,7 +47,7 @@
// 生产订单下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    url: "/productionOrderRouting/sortRouteItem",
    method: "post",
    data,
  });
@@ -55,7 +55,7 @@
// 获取工序参数列表-生产订单
export function findProcessParamListOrder(query) {
  return request({
    url: `/productionOrderRouteItemParam/list`,
    url: `/productionOrderRoutingOperationParam/list`,
    method: "get",
    params: query,
  });
@@ -63,7 +63,7 @@
// 工艺路线参数新增-生产订单
export function addProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/add",
    url: "/productionOrderRoutingOperationParam",
    method: "post",
    data: data,
  });
@@ -71,7 +71,7 @@
// 工艺路线参数修改-生产订单
export function editProcessRouteItemParamOrder(data) {
  return request({
    url: "/productionOrderRouteItemParam/update",
    url: "/productionOrderRoutingOperationParam",
    method: "put",
    data: data,
  });
@@ -79,7 +79,7 @@
// 工艺路线参数删除-生产订单
export function delProcessRouteItemParamOrder(id) {
  return request({
    url: `/productionOrderRouteItemParam/delete/${id}`,
    url: `/productionOrderRoutingOperationParam/${id}`,
    method: "delete",
  });
}
src/api/productionManagement/productionOrder.js
@@ -30,7 +30,7 @@
// 生产订单-绑定工艺路线
export function bindingRoute(data) {
  return request({
    url: "/productOrder/bindingRoute",
    url: "/productionOrder/bindingRoute",
    method: "post",
    data,
  });
@@ -39,7 +39,7 @@
// 生产订单-新增
export function addProductOrder(data) {
  return request({
    url: "/productionOrder",
    url: "/productionOrder/addOrder",
    method: "post",
    data: data,
  });
@@ -47,7 +47,7 @@
export function delProductOrder(ids) {
  return request({
    url: `/productOrder/${ids}`,
    url: `/productionOrder/delete/${ids}`,
    method: "delete",
  });
}
src/components/AttachmentPreview/image/index.vue
@@ -1,6 +1,6 @@
<script setup>
const props = defineProps({
  list: {
  fileList: {
    type: Array,
    default: () => [],
  },
@@ -15,7 +15,7 @@
})
const normalizedList = computed(() => {
  return (props.list || [])
  return (props.fileList || [])
    .filter((item) => item && item.previewURL)
    .map((item, index) => ({
      id: item.id ?? index,
src/components/AttachmentUpload/image/index.vue
@@ -88,7 +88,7 @@
})
const uploadTip = computed(() => {
  return `支持 ${props.fileType.join('/')},单张不超过 ${props.fileSize}MB`
  return `支持 ${props.fileType.join('/')},单张不超过 ${props.fileSize}MB,最多上传 ${props.limit} 张图片`
})
function getItemUid(item, index) {
src/components/ProcessParamListDialog.vue
@@ -374,10 +374,9 @@
    // 调用API新增参数
    if (props.pageType === "order") {
      addProcessRouteItemParamOrder({
        orderId: Number(props.orderId),
        // processId: props.process.id,
        routeItemId: props.process.id,
        // routeItemId: Number(props.routeId),
        productionOrderId: Number(props.orderId),
        productionOrderRoutingOperationId: props.process.id,
        technologyRoutingOperationParamId: props.process.id,
        paramId: selectedParam.value.id,
        standardValue: selectedParam.value.standardValue || "",
        isRequired: selectedParam.value.isRequired || 0,
@@ -428,8 +427,6 @@
        if (props.pageType === "order") {
          editProcessRouteItemParamOrder({
            id: editParamForm.value.id,
            // routeItemId: props.process.id,
            // paramId: editParamForm.value.paramId,
            standardValue: editParamForm.value.standardValue || "",
            isRequired: editParamForm.value.isRequired || 0,
          })
src/main.js
@@ -43,11 +43,11 @@
// 富文本组件
import Editor from "@/components/Editor";
// 文件上传组件
import FileUpload from "@/components/FileUpload";
import FileUpload from "@/components/AttachmentUpload/file";
// 图片上传组件
import ImageUpload from "@/components/ImageUpload";
import ImageUpload from "@/components/AttachmentUpload/image";
// 图片预览组件
import ImagePreview from "@/components/ImagePreview";
import ImagePreview from "@/components/AttachmentPreview/image";
// 字典标签组件
import DictTag from "@/components/DictTag";
// 表格组件
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -134,40 +134,6 @@
const currentMediaIndex = ref(0);
const mediaList = ref([]); // 存储当前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
const javaApi = proxy.javaApi;
// 处理 URL:将 Windows 路径转换为可访问的 URL
function processFileUrl(fileUrl) {
  if (!fileUrl) return '';
  // 如果 URL 是 Windows 路径格式(包含反斜杠),需要转换
  if (fileUrl && fileUrl.indexOf('\\') > -1) {
    // 查找 uploads 关键字的位置,从那里开始提取相对路径
    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
    if (uploadsIndex > -1) {
      // 从 uploads 开始提取路径,并将反斜杠替换为正斜杠
      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
      fileUrl = '/' + relativePath;
    } else {
      // 如果没有找到 uploads,提取最后一个目录和文件名
      const parts = fileUrl.split('\\');
      const fileName = parts[parts.length - 1];
      fileUrl = '/uploads/' + fileName;
    }
  }
  // 确保所有非 http 开头的 URL 都拼接 baseUrl
  if (fileUrl && !fileUrl.startsWith('http')) {
    // 确保路径以 / 开头
    if (!fileUrl.startsWith('/')) {
      fileUrl = '/' + fileUrl;
    }
    // 拼接 baseUrl
    fileUrl = javaApi + fileUrl;
  }
  return fileUrl;
}
// 处理每一类数据:分离图片和视频
function processItems(items) {
@@ -180,24 +146,18 @@
  }
  
  items.forEach(item => {
    if (!item || !item.url) return;
    if (!item || !item.previewURL || !item.contentType) return;
    
    // 处理文件 URL
    const fileUrl = processFileUrl(item.url);
    // 根据文件扩展名判断是图片还是视频
    const urlLower = fileUrl.toLowerCase();
    if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) {
    const fileUrl = item.previewURL;
    const contentType = String(item.contentType).toLowerCase();
    // 根据 contentType 判断是图片还是视频
    if (contentType.startsWith('image/')) {
      images.push(fileUrl);
    } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) {
    } else if (contentType.startsWith('video/')) {
      videos.push(fileUrl);
    } else if (item.contentType) {
      // 如果有 contentType,使用 contentType 判断
      if (item.contentType.startsWith('image/')) {
        images.push(fileUrl);
      } else if (item.contentType.startsWith('video/')) {
        videos.push(fileUrl);
      }
    }
  });
  
@@ -207,10 +167,9 @@
// 打开弹窗并加载数据
const openDialog = async (row) => {
  // 使用正确的字段名:commonFileListBefore, commonFileListAfter
  // productionIssues 可能不存在,使用空数组
  const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []);
  const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []);
  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []);
  const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []);
  const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []);
  const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []);
  
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
src/views/index.vue
@@ -8,7 +8,7 @@
          <!-- 顶部问候条 -->
          <div class="welcome-banner">
            <div class="welcome-title">
              <span class="welcome-user">{{ userStore.roleName || '系统管理员' }}</span>
              <span class="welcome-user">{{ userStore.nickName || '系统管理员' }}</span>
              <span> 您好!祝您开心每一天</span>
            </div>
            <div class="welcome-time">登录于: {{ userStore.currentLoginTime }}</div>
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -232,7 +232,7 @@
              style="width: 100%">
      <el-table-column type="expand">
        <template #default>
          <el-form ref="form"
          <el-form ref="bomFormRef"
                   :model="bomDataValue">
            <el-table :data="bomDataValue.dataList"
                      row-key="tempId"
@@ -356,7 +356,7 @@
                       prop="model" />
    </el-table>
    <ProductSelectDialog v-if="bomDataValue.showProductDialog"
                         v-model:model-value="bomDataValue.showProductDialog"
                         v-model="bomDataValue.showProductDialog"
                         :single="true"
                         @confirm="handleBomProduct" />
    <!-- 新增/编辑弹窗 -->
@@ -385,8 +385,8 @@
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName && form.model
              ? `${form.productName} - ${form.model}`
            {{ form.productName
              ? (form.model ? `${form.productName} - ${form.model}` : form.productName)
              : '选择产品' }}
          </el-button>
        </el-form-item>
@@ -401,13 +401,13 @@
                      prop="isQuality">
          <el-switch v-model="form.isQuality"
                     :active-value="true"
                     inactive-value="false" />
                     :inactive-value="false" />
        </el-form-item>
        <el-form-item label="是否生产"
                      prop="isProduction">
          <el-switch v-model="form.isProduction"
                     :active-value="true"
                     inactive-value="false" />
                     :inactive-value="false" />
        </el-form-item>
      </el-form>
      <template #footer>
@@ -454,7 +454,10 @@
    batchDeleteProcessRouteItem,
    getProcessParamList,
  } from "@/api/productionManagement/processRouteItem.js";
  import { syncProcessParamItem } from "@/api/productionManagement/processRouteItem.js";
  import {
    syncProcessParamItem,
    syncProcessParamItemOrder,
  } from "@/api/productionManagement/processRouteItem.js";
  import {
    findProductProcessRouteItemList,
    deleteRouteItem,
@@ -469,6 +472,7 @@
    queryList,
    addBomDetail,
  } from "@/api/productionManagement/productStructure.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
  import Sortable from "sortablejs";
@@ -485,6 +489,7 @@
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const bomFormRef = ref(null);
  const submitLoading = ref(false);
  const cardsContainer = ref(null);
  const tableRef = ref(null);
@@ -542,17 +547,31 @@
      type: "warning",
    })
      .then(() => {
        syncProcessParamItem({
          replaceExisting: true,
          technologyRoutingOperationId: currentProcess.value.id,
        }).then(res => {
          if (res.code === 200) {
            ElMessage.success("同步成功");
            refreshParamList();
          } else {
            ElMessage.error(res.msg || "同步失败");
          }
        });
        if (pageType.value === "order") {
          syncProcessParamItemOrder({
            replaceExisting: true,
            technologyRoutingOperationId: currentProcess.value.id,
          }).then(res => {
            if (res.code === 200) {
              ElMessage.success("同步成功");
              refreshParamList();
            } else {
              ElMessage.error(res.msg || "同步失败");
            }
          });
        } else {
          syncProcessParamItem({
            replaceExisting: true,
            technologyRoutingOperationId: currentProcess.value.id,
          }).then(res => {
            if (res.code === 200) {
              ElMessage.success("同步成功");
              refreshParamList();
            } else {
              ElMessage.error(res.msg || "同步失败");
            }
          });
        }
      })
      .catch(() => {});
  };
@@ -670,15 +689,20 @@
  // 产品选择
  const handleProductSelect = products => {
    console.log(products, "===products===");
    if (products && products.length > 0) {
      const product = products[0];
      form.value.productModelId = product.id;
      form.value.productName = product.productName;
      form.value.model = product.model;
      form.value.unit = product.unit || "";
      console.log(product, "product");
      form.value = {
        ...form.value,
        productModelId: product.id,
        productName: product.productName,
        model: product.model,
        unit: product.unit || "",
      };
      showProductSelectDialog.value = false;
      // 触发表单验证
      formRef.value?.validateField("productModelId");
      // formRef.value?.validateField("productModelId");
    }
  };
@@ -773,6 +797,8 @@
      productName: "",
      model: "",
      unit: "",
      isQuality: false,
      isProduction: false,
    };
    formRef.value?.resetFields();
  };
@@ -788,7 +814,7 @@
    currentProcess.value = row;
    const query = {
      technologyRoutingOperationId: row.id,
      orderId: orderId.value,
      productionOrderId: orderId.value,
    };
    const apiPromise =
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -1,298 +1,327 @@
<template>
  <div>
    <el-dialog v-model="dialogVisible" title="领料台账" width="1200px" @close="handleClose">
    <el-dialog v-model="dialogVisible"
               title="领料台账"
               width="1200px"
               @close="handleClose">
      <div class="material-toolbar">
        <el-button type="primary" @click="handleAddMaterialRow">新增</el-button>
        <el-button type="primary"
                   @click="handleAddMaterialRow">新增</el-button>
      </div>
      <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="tempId">
        <el-table-column label="工序名称" min-width="180">
      <el-table v-loading="materialTableLoading"
                :data="materialTableData"
                border
                row-key="tempId">
        <el-table-column label="工序名称"
                         min-width="180">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.processName || "-" }}</span>
            <el-select
              v-else
              v-model="row.processName"
              placeholder="请选择工序"
              clearable
              filterable
              style="width: 100%;"
              @change="val => handleProcessNameChange(row, val)"
            >
              <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.name" />
            <el-select v-else
                       v-model="row.processName"
                       placeholder="请选择工序"
                       clearable
                       filterable
                       style="width: 100%;"
                       @change="val => handleProcessNameChange(row, val)">
              <el-option v-for="item in processOptions"
                         :key="item.id"
                         :label="item.name"
                         :value="item.name" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="原料名称" min-width="160">
        <el-table-column label="原料名称"
                         min-width="160">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
            <el-button v-else type="primary" link @click="openMaterialProductSelect(row)">
            <el-button v-else
                       type="primary"
                       link
                       @click="openMaterialProductSelect(row)">
              {{ row.materialName || "选择原料" }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column label="原料型号" min-width="180">
        <el-table-column label="原料型号"
                         min-width="180">
          <template #default="{ row }">
            {{ row.materialModel || "-" }}
          </template>
        </el-table-column>
        <el-table-column label="需求数量" min-width="120">
        <el-table-column label="需求数量"
                         min-width="120">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.requiredQty ?? "-" }}</span>
            <el-input-number
              v-else
              v-model="row.requiredQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
              @change="val => handleRequiredQtyChange(row, val)"
            />
            <el-input-number v-else
                             v-model="row.requiredQty"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             style="width: 100%;"
                             @change="val => handleRequiredQtyChange(row, val)" />
          </template>
        </el-table-column>
        <el-table-column label="计量单位" width="120">
        <el-table-column label="计量单位"
                         width="120">
          <template #default="{ row }">
            {{ row.unit || "-" }}
          </template>
        </el-table-column>
        <el-table-column label="领用数量" min-width="120">
        <el-table-column label="领用数量"
                         min-width="120">
          <template #default="{ row }">
            <el-input-number
              v-model="row.pickQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
            />
            <el-input-number v-model="row.pickQty"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             style="width: 100%;" />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="90" fixed="right">
        <el-table-column label="操作"
                         width="90"
                         fixed="right">
          <template #default="{ $index, row }">
            <el-button v-if="row.bom !== true" type="danger" link @click="handleDeleteMaterialRow($index)">删除</el-button>
            <el-button v-if="row.bom !== true"
                       type="danger"
                       link
                       @click="handleDeleteMaterialRow($index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="materialSaving" @click="handleMaterialSave">保存</el-button>
          <el-button type="primary"
                     :loading="materialSaving"
                     @click="handleMaterialSave">保存</el-button>
          <el-button @click="dialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <ProductSelectDialog
      v-model="materialProductDialogVisible"
      @confirm="handleMaterialProductConfirm"
      single
      request-url="/stockInventory/rawMaterials"
    />
    <ProductSelectDialog v-model="materialProductDialogVisible"
                         @confirm="handleMaterialProductConfirm"
                         single />
    <!-- request-url="/stockInventory/rawMaterials" -->
  </div>
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
import {
  listMaterialPickingDetail,
  listMaterialPickingLedger,
  saveMaterialPickingLedger,
} from "@/api/productionManagement/productionOrder.js";
  import { computed, ref, watch } from "vue";
  import { ElMessage } from "element-plus";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
  import {
    listMaterialPickingDetail,
    listMaterialPickingLedger,
    saveMaterialPickingLedger,
  } from "@/api/productionManagement/productionOrder.js";
const props = defineProps({
  modelValue: { type: Boolean, default: false },
  orderRow: { type: Object, default: null },
});
const emit = defineEmits(["update:modelValue", "saved"]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: val => emit("update:modelValue", val),
});
const materialProductDialogVisible = ref(false);
const materialTableLoading = ref(false);
const materialSaving = ref(false);
const materialTableData = ref([]);
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
const createMaterialRow = (row = {}) => ({
  tempId: row.id || `temp_${++materialTempId}`,
  id: row.id,
  processId: row.processId,
  productProcessId: row.productProcessId || row.processId,
  processName: row.processName || "",
  bom: row.bom === true,
  materialModelId: row.materialModelId,
  materialName: row.materialName || "",
  materialModel: row.materialModel || "",
  requiredQty: Number(row.requiredQty ?? 0),
  unit: row.unit || "",
  pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
});
const getProcessOptions = async () => {
  if (!props.orderRow?.id) return;
  const res = await findProductProcessRouteItemList({ orderId: props.orderRow.id });
  const routeList = Array.isArray(res?.data) ? res.data : res?.data?.records || [];
  const processMap = new Map();
  routeList.forEach(item => {
    const processId = item.processId;
    const processName = item.processName;
    if (!processId || !processName) return;
    const key = `${processId}_${processName}`;
    if (!processMap.has(key)) {
      processMap.set(key, {
        id: processId,
        name: processName,
      });
    }
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
    orderRow: { type: Object, default: null },
  });
  processOptions.value = Array.from(processMap.values());
};
  const emit = defineEmits(["update:modelValue", "saved"]);
const loadMaterialData = async () => {
  if (!props.orderRow?.id) return;
  materialTableLoading.value = true;
  materialTableData.value = [];
  await getProcessOptions();
  try {
    const detailRes = await listMaterialPickingDetail({ orderId: props.orderRow.id });
    const detailList = Array.isArray(detailRes?.data)
      ? detailRes.data
      : detailRes?.data?.records || [];
    if (detailList.length > 0) {
      materialTableData.value = detailList.map(item => createMaterialRow(item));
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const materialProductDialogVisible = ref(false);
  const materialTableLoading = ref(false);
  const materialSaving = ref(false);
  const materialTableData = ref([]);
  const processOptions = ref([]);
  const currentMaterialSelectRowIndex = ref(-1);
  let materialTempId = 0;
  const createMaterialRow = (row = {}) => ({
    tempId: row.id || `temp_${++materialTempId}`,
    id: row.id,
    processId: row.processId,
    productProcessId: row.productProcessId || row.processId,
    processName: row.processName || "",
    bom: row.bom === true,
    materialModelId: row.materialModelId,
    materialName: row.materialName || "",
    materialModel: row.materialModel || "",
    requiredQty: Number(row.requiredQty ?? 0),
    unit: row.unit || "",
    pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
  });
  const getProcessOptions = async () => {
    if (!props.orderRow?.id) return;
    const res = await findProductProcessRouteItemList({
      orderId: props.orderRow.id,
    });
    const routeList = Array.isArray(res?.data)
      ? res.data
      : res?.data?.records || [];
    const processMap = new Map();
    routeList.forEach(item => {
      const processId = item.processId;
      const processName = item.processName;
      if (!processId || !processName) return;
      const key = `${processId}_${processName}`;
      if (!processMap.has(key)) {
        processMap.set(key, {
          id: processId,
          name: processName,
        });
      }
    });
    processOptions.value = Array.from(processMap.values());
  };
  const loadMaterialData = async () => {
    if (!props.orderRow?.id) return;
    materialTableLoading.value = true;
    materialTableData.value = [];
    await getProcessOptions();
    try {
      const detailRes = await listMaterialPickingDetail({
        orderId: props.orderRow.id,
      });
      const detailList = Array.isArray(detailRes?.data)
        ? detailRes.data
        : detailRes?.data?.records || [];
      if (detailList.length > 0) {
        materialTableData.value = detailList.map(item => createMaterialRow(item));
        return;
      }
      const ledgerRes = await listMaterialPickingLedger({
        orderId: props.orderRow.id,
      });
      const ledgerList = Array.isArray(ledgerRes?.data)
        ? ledgerRes.data
        : ledgerRes?.data?.records || [];
      materialTableData.value = ledgerList.map(item => createMaterialRow(item));
    } finally {
      materialTableLoading.value = false;
    }
  };
  watch(
    () => dialogVisible.value,
    visible => {
      if (visible) {
        loadMaterialData();
      }
    }
  );
  const handleClose = () => {
    materialTableData.value = [];
    currentMaterialSelectRowIndex.value = -1;
  };
  const handleAddMaterialRow = () => {
    materialTableData.value.push(createMaterialRow());
  };
  const handleDeleteMaterialRow = index => {
    materialTableData.value.splice(index, 1);
  };
  const handleProcessNameChange = (row, processName) => {
    const process = processOptions.value.find(item => item.name === processName);
    row.productProcessId = process?.id;
  };
  const handleRequiredQtyChange = (row, val) => {
    const required = Number(val ?? 0);
    row.requiredQty = required;
    if (!row.pickQty || Number(row.pickQty) === 0) {
      row.pickQty = required;
    }
  };
  const openMaterialProductSelect = row => {
    currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(
      item => item.tempId === row.tempId
    );
    materialProductDialogVisible.value = true;
  };
  const handleMaterialProductConfirm = products => {
    if (!products || products.length === 0) return;
    const index = currentMaterialSelectRowIndex.value;
    if (index < 0 || !materialTableData.value[index]) return;
    const product = products[0];
    const row = materialTableData.value[index];
    row.materialModelId =
      product.materialModelId || product.modelId || product.id;
    row.materialName =
      product.materialName || product.productName || product.name || "";
    row.materialModel = product.materialModel || product.model || "";
    row.unit = product.unit || product.measureUnit || "";
    currentMaterialSelectRowIndex.value = -1;
    materialProductDialogVisible.value = false;
  };
  const validateMaterialRows = () => {
    if (materialTableData.value.length === 0) {
      return { valid: false, message: "请先新增领料数据" };
    }
    const invalidNewRow = materialTableData.value.find(
      item => item.bom !== true && (!item.processName || !item.materialName)
    );
    if (invalidNewRow) {
      return { valid: false, message: "新增行的工序名称和原料名称为必填项" };
    }
    const invalidRow = materialTableData.value.find(
      item =>
        !item.processName ||
        !item.materialName ||
        item.requiredQty === null ||
        item.requiredQty === undefined ||
        item.pickQty === null ||
        item.pickQty === undefined
    );
    if (invalidRow) {
      return { valid: false, message: "请完善工序、原料和数量后再保存" };
    }
    return { valid: true, message: "" };
  };
  const handleMaterialSave = async () => {
    if (!props.orderRow?.id) return;
    const validateResult = validateMaterialRows();
    if (!validateResult.valid) {
      ElMessage.warning(validateResult.message);
      return;
    }
    const ledgerRes = await listMaterialPickingLedger({ orderId: props.orderRow.id });
    const ledgerList = Array.isArray(ledgerRes?.data)
      ? ledgerRes.data
      : ledgerRes?.data?.records || [];
    materialTableData.value = ledgerList.map(item => createMaterialRow(item));
  } finally {
    materialTableLoading.value = false;
  }
};
watch(
  () => dialogVisible.value,
  visible => {
    if (visible) {
      loadMaterialData();
    materialSaving.value = true;
    try {
      await saveMaterialPickingLedger({
        orderId: props.orderRow.id,
        items: materialTableData.value.map(item => ({
          id: item.id,
          processId: item.processName,
          productProcessId: item.productProcessId,
          processName: item.processName,
          bom: item.bom === true,
          materialModelId: item.materialModelId,
          materialName: item.materialName,
          materialModel: item.materialModel,
          requiredQty: item.requiredQty,
          unit: item.unit,
          pickQty: item.pickQty,
        })),
      });
      emit("saved");
      dialogVisible.value = false;
    } finally {
      materialSaving.value = false;
    }
  }
);
const handleClose = () => {
  materialTableData.value = [];
  currentMaterialSelectRowIndex.value = -1;
};
const handleAddMaterialRow = () => {
  materialTableData.value.push(createMaterialRow());
};
const handleDeleteMaterialRow = index => {
  materialTableData.value.splice(index, 1);
};
const handleProcessNameChange = (row, processName) => {
  const process = processOptions.value.find(item => item.name === processName);
  row.productProcessId = process?.id;
};
const handleRequiredQtyChange = (row, val) => {
  const required = Number(val ?? 0);
  row.requiredQty = required;
  if (!row.pickQty || Number(row.pickQty) === 0) {
    row.pickQty = required;
  }
};
const openMaterialProductSelect = row => {
  currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(item => item.tempId === row.tempId);
  materialProductDialogVisible.value = true;
};
const handleMaterialProductConfirm = products => {
  if (!products || products.length === 0) return;
  const index = currentMaterialSelectRowIndex.value;
  if (index < 0 || !materialTableData.value[index]) return;
  const product = products[0];
  const row = materialTableData.value[index];
  row.materialModelId = product.materialModelId || product.modelId || product.id;
  row.materialName = product.materialName || product.productName || product.name || "";
  row.materialModel = product.materialModel || product.model || "";
  row.unit = product.unit || product.measureUnit || "";
  currentMaterialSelectRowIndex.value = -1;
  materialProductDialogVisible.value = false;
};
const validateMaterialRows = () => {
  if (materialTableData.value.length === 0) {
    return { valid: false, message: "请先新增领料数据" };
  }
  const invalidNewRow = materialTableData.value.find(
    item => item.bom !== true && (!item.processName || !item.materialName)
  );
  if (invalidNewRow) {
    return { valid: false, message: "新增行的工序名称和原料名称为必填项" };
  }
  const invalidRow = materialTableData.value.find(
    item =>
      !item.processName ||
      !item.materialName ||
      item.requiredQty === null ||
      item.requiredQty === undefined ||
      item.pickQty === null ||
      item.pickQty === undefined
  );
  if (invalidRow) {
    return { valid: false, message: "请完善工序、原料和数量后再保存" };
  }
  return { valid: true, message: "" };
};
const handleMaterialSave = async () => {
  if (!props.orderRow?.id) return;
  const validateResult = validateMaterialRows();
  if (!validateResult.valid) {
    ElMessage.warning(validateResult.message);
    return;
  }
  materialSaving.value = true;
  try {
    await saveMaterialPickingLedger({
      orderId: props.orderRow.id,
      items: materialTableData.value.map(item => ({
        id: item.id,
        processId: item.processName,
        productProcessId: item.productProcessId,
        processName: item.processName,
        bom: item.bom === true,
        materialModelId: item.materialModelId,
        materialName: item.materialName,
        materialModel: item.materialModel,
        requiredQty: item.requiredQty,
        unit: item.unit,
        pickQty: item.pickQty,
      })),
    });
    emit("saved");
    dialogVisible.value = false;
  } finally {
    materialSaving.value = false;
  }
};
  };
</script>
<style scoped lang="scss">
.material-toolbar {
  margin-bottom: 12px;
  text-align: right;
}
  .material-toolbar {
    margin-bottom: 12px;
    text-align: right;
  }
</style>
src/views/productionManagement/productionOrder/index.vue
@@ -28,7 +28,7 @@
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productCategory"
          <el-input v-model="searchForm.productName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
@@ -36,7 +36,7 @@
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
          <el-input v-model="searchForm.specificationModel"
          <el-input v-model="searchForm.model"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
@@ -269,8 +269,8 @@
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
      productName: "",
      model: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -354,7 +354,7 @@
    try {
      await bindingRoute({
        id: bindForm.orderId,
        routeId: bindForm.routeId,
        technologyRoutingId: bindForm.routeId,
      });
      proxy.$modal.msgSuccess("绑定成功");
      bindRouteDialogVisible.value = false;
@@ -429,9 +429,9 @@
          id: data.id,
          bomId: data.bomId,
          processRouteCode: data.processRouteCode || "",
          productName: data.productName || "",
          model: data.model || "",
          bomNo: data.bomNo || "",
          productName: row.productName || "",
          model: row.model || "",
          bomNo: row.bomNo || "",
          description: data.description || "",
          orderId,
          type: "order",
@@ -449,8 +449,8 @@
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productCategory || "",
        productModelName: row.specificationModel || "",
        productName: row.productName || "",
        productModelName: row.model || "",
        orderId: row.id,
        type: "order",
      },
src/views/productionPlan/productionPlan/index.vue
@@ -343,7 +343,7 @@
    },
    {
      label: "已下发数量",
      prop: "assignedQuantity",
      prop: "quantityIssued",
      width: "120px",
      className: "spec-cell",
      // formatData: (cell, row) => (cell ? `${cell}${row.unit || "方"}` : 0),
@@ -389,16 +389,18 @@
          name: "下发",
          type: "text",
          showHide: row => {
            return row.status == 0;
            return row.status != 2;
          },
          clickFun: row => {
            mergeForm.productName = row.productName || "";
            mergeForm.model = row.model || "";
            mergeForm.totalAssignedQuantity = Number(row.qtyRequired || 0);
            mergeForm.totalAssignedQuantity =
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
            mergeForm.planCompleteTime = row.requiredDate || "";
            mergeForm.productId = row.productId || "";
            mergeForm.ids = [row.id];
            sumAssignedQuantity.value = Number(row.qtyRequired || 0);
            sumAssignedQuantity.value =
              Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
            isShowNewModal.value = true;
          },
        },
@@ -618,9 +620,9 @@
        };
      }
      summary[category].totalAssignedQuantity += Number(
        (
          Number(row.qtyRequired || 0) - Number(row.assignedQuantity || 0)
        ).toFixed(4)
        (Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)).toFixed(
          4
        )
      );
    });
@@ -667,8 +669,12 @@
  // 判断行是否可选择
  const isSelectable = row => {
    // 如果是已下发状态,禁止勾选
    if (row.status == 2) {
      return false;
    }
    // 计算剩余数量
    const remainingQty = (row.qtyRequired || 0) - (row.assignedQuantity || 0);
    const remainingQty = (row.qtyRequired || 0) - (row.quantityIssued || 0);
    // 如果剩余数量小于等于0,禁止选择
    if (remainingQty <= 0) {
      return false;
@@ -696,7 +702,7 @@
    // 计算总制造数量 (默认qtyRequired的和)
    const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
      return sum + Number(row.qtyRequired || 0);
      return sum + Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
    }, 0);
    sumAssignedQuantity.value = totalAssignedQuantity;
    console.log(totalAssignedQuantity);
src/views/salesManagement/deliveryLedger/index.vue
@@ -3,19 +3,22 @@
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="销售订单号:">
          <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" style="width: 200px"
            @change="handleQuery" />
          <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px"
                    @change="handleQuery"/>
        </el-form-item>
        <el-form-item label="车牌号:">
          <el-input v-model="searchForm.shippingCarNumber" placeholder="请输入" clearable prefix-icon="Search" style="width: 200px"
            @change="handleQuery" />
          <el-input v-model="searchForm.shippingCarNumber" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px"
                    @change="handleQuery"/>
        </el-form-item>
        <el-form-item label="快递单号:">
          <el-input v-model="searchForm.expressNumber" placeholder="请输入" clearable prefix-icon="Search" style="width: 200px"
            @change="handleQuery" />
          <el-input v-model="searchForm.expressNumber" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px"
                    @change="handleQuery"/>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery"> 搜索 </el-button>
          <el-button type="primary" @click="handleQuery"> 搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
@@ -28,18 +31,18 @@
        </div>
      </div>
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 21.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="销售订单" prop="salesContractNo" show-overflow-tooltip />
        <el-table-column label="发货订单号" prop="shippingNo" show-overflow-tooltip />
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip />
        <el-table-column label="产品名称" prop="productName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
        <el-table-column label="发货时间" prop="shippingDate" show-overflow-tooltip />
        <el-table-column label="发货车牌号" prop="shippingCarNumber" show-overflow-tooltip />
        <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip />
        <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip />
                :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 21.5em)">
        <el-table-column align="center" type="selection" width="55"/>
        <el-table-column align="center" label="序号" type="index" width="60"/>
        <el-table-column label="销售订单" prop="salesContractNo" show-overflow-tooltip/>
        <el-table-column label="发货订单号" prop="shippingNo" show-overflow-tooltip/>
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip/>
        <el-table-column label="产品名称" prop="productName" show-overflow-tooltip/>
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip/>
        <el-table-column label="发货时间" prop="shippingDate" show-overflow-tooltip/>
        <el-table-column label="发货车牌号" prop="shippingCarNumber" show-overflow-tooltip/>
        <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip/>
        <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip/>
        <el-table-column label="审核状态" prop="status" align="center" width="120">
          <template #default="scope">
            <el-tag :type="getApprovalStatusType(scope.row.status)">
@@ -49,42 +52,46 @@
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="220" align="center">
          <template #default="scope">
            <el-button
              link
              type="primary"
              :disabled="!isApproved(scope.row.status)"
              @click="openForm('edit', scope.row)">补充发货信息</el-button>
            <el-button
              link
              type="primary"
                            style="color: #67C23A"
              @click="openDetail(scope.row)"
            >详情</el-button>
            <el-button
              link
              type="danger"
              :disabled="isApproving(scope.row.status)"
              @click="handleDeleteSingle(scope.row)">删除</el-button>
                link
                type="primary"
                :disabled="!isApproved(scope.row.status)"
                @click="openForm('edit', scope.row)">补充发货信息
            </el-button>
            <el-button
                link
                type="primary"
                style="color: #67C23A"
                @click="openDetail(scope.row)"
            >详情
            </el-button>
            <el-button
                link
                type="danger"
                :disabled="isApproving(scope.row.status)"
                @click="handleDeleteSingle(scope.row)">删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
                  :page="page.current" :limit="page.size" @pagination="paginationChange"/>
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增发货台账' : '编辑发货台账'" width="40%"
      @close="closeDia">
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增发货台账' : '编辑发货台账'"
               width="40%"
               @close="closeDia">
      <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="发货类型:" prop="type">
              <el-select
                v-model="form.type"
                placeholder="请选择发货类型"
                style="width: 100%"
                @change="handleShippingTypeChange"
                  v-model="form.type"
                  placeholder="请选择发货类型"
                  style="width: 100%"
                  @change="handleShippingTypeChange"
              >
                <el-option label="货车" value="货车" />
                <el-option label="快递" value="快递" />
                <el-option label="货车" value="货车"/>
                <el-option label="快递" value="快递"/>
              </el-select>
            </el-form-item>
          </el-col>
@@ -93,13 +100,13 @@
          <el-col :span="24">
            <el-form-item label="发货日期:" prop="shippingDate">
              <el-date-picker
                style="width: 100%"
                v-model="form.shippingDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择发货日期"
                clearable
                  style="width: 100%"
                  v-model="form.shippingDate"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择发货日期"
                  clearable
              />
            </el-form-item>
          </el-col>
@@ -108,18 +115,18 @@
          <el-col :span="24" v-if="form.type === '货车'">
            <el-form-item label="发货车牌号:" prop="shippingCarNumber">
              <el-input
                v-model="form.shippingCarNumber"
                placeholder="请输入发货车牌号"
                clearable
                  v-model="form.shippingCarNumber"
                  placeholder="请输入发货车牌号"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="24" v-else>
            <el-form-item label="快递公司:" prop="expressCompany">
              <el-input
                v-model="form.expressCompany"
                placeholder="请输入快递公司"
                clearable
                  v-model="form.expressCompany"
                  placeholder="请输入快递公司"
                  clearable
              />
            </el-form-item>
          </el-col>
@@ -128,9 +135,9 @@
          <el-col :span="24">
            <el-form-item label="快递单号:" prop="expressNumber">
              <el-input
                v-model="form.expressNumber"
                placeholder="请输入快递单号"
                clearable
                  v-model="form.expressNumber"
                  placeholder="请输入快递单号"
                  clearable
              />
            </el-form-item>
          </el-col>
@@ -138,29 +145,7 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="发货图片:">
              <el-upload
                v-model:file-list="deliveryFileList"
                :action="upload.url"
                multiple
                ref="deliveryFileUpload"
                auto-upload
                :headers="upload.headers"
                :data="{ type: 9 }"
                :before-upload="handleDeliveryBeforeUpload"
                :on-error="handleDeliveryUploadError"
                :on-success="handleDeliveryUploadSuccess"
                :on-remove="handleDeliveryRemove"
                list-type="picture-card"
                :limit="9"
                accept="image/png,image/jpeg,image/jpg"
              >
                <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
                <template #tip>
                  <div class="el-upload__tip">
                    支持 jpg、jpeg、png 格式,最多上传 9 张,单张大小不超过 10MB
                  </div>
                </template>
              </el-upload>
              <ImageUpload v-model:file-list="deliveryFileList" :limit="9"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -189,19 +174,7 @@
          <el-descriptions-item label="快递公司">{{ detailRow.expressCompany || '--' }}</el-descriptions-item>
          <el-descriptions-item label="快递单号" :span="2">{{ detailRow.expressNumber || '--' }}</el-descriptions-item>
        </el-descriptions>
        <div class="detail-images" v-if="detailImages.length">
          <div class="detail-images-title">发货图片</div>
          <el-image
            v-for="img in detailImages"
            :key="img.url"
            :src="img.url"
            :preview-src-list="detailImages.map(i => i.url)"
            fit="cover"
            class="detail-image"
          />
        </div>
        <div v-else class="detail-images-empty">暂无发货图片</div>
        <ImagePreview :file-list="detailRow.storageBlobVOs || []" />
      </div>
      <template #footer>
        <div class="dialog-footer">
@@ -214,20 +187,19 @@
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth";
import { getCurrentDate } from "@/utils/index.js";
import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import {getCurrentDate} from "@/utils/index.js";
import {
    deliveryLedgerListPage,
    addOrUpdateDeliveryLedger,
    delDeliveryLedger, deductStock,
  deliveryLedgerListPage,
  delDeliveryLedger, deductStock,
} from "@/api/salesManagement/deliveryLedger.js";
import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
import {delLedgerFile} from "@/api/salesManagement/salesLedger.js";
import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
import ImagePreview from "@/components/AttachmentPreview/image/index.vue";
const { proxy } = getCurrentInstance();
const {proxy} = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
@@ -238,40 +210,9 @@
});
const total = ref(0);
const deliveryFileList = ref([]);
const javaApi = proxy.javaApi;
// 详情弹框
const detailDialogVisible = ref(false);
const detailRow = ref(null);
const detailImages = ref([]);
const normalizeFileUrl = (rawUrl = '') => {
  let fileUrl = rawUrl || '';
  // Windows 路径转 URL
  if (fileUrl && fileUrl.indexOf('\\') > -1) {
    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
    if (uploadsIndex > -1) {
      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
      fileUrl = '/' + relativePath;
    } else {
      const parts = fileUrl.split('\\');
      const fileName = parts[parts.length - 1];
      fileUrl = '/uploads/' + fileName;
    }
  }
  if (fileUrl && !fileUrl.startsWith('http')) {
    if (!fileUrl.startsWith('/')) fileUrl = '/' + fileUrl;
    fileUrl = javaApi + fileUrl;
  }
  return fileUrl;
};
// 上传配置
const upload = reactive({
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
// 用户信息表单弹框数据
const operationType = ref("");
@@ -295,24 +236,23 @@
    expressNumber: "", // 快递单号
  },
  rules: {
    salesContractNo: [{ required: true, message: "请选择销售订单", trigger: "change" }],
    customerName: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
    salesContractNo: [{required: true, message: "请选择销售订单", trigger: "change"}],
    customerName: [{required: true, message: "请输入客户名称", trigger: "blur"}],
    type: [
      { required: true, message: "请选择发货类型", trigger: "change" }
      {required: true, message: "请选择发货类型", trigger: "change"}
    ],
    shippingDate: [{ required: true, message: "请选择发货时间", trigger: "change" }],
    shippingDate: [{required: true, message: "请选择发货时间", trigger: "change"}],
    shippingCarNumber: [
      { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" }
      {validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur"}
    ],
    expressCompany: [
      { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" }
      {validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur"}
    ],
  },
});
const { form, rules } = toRefs(data);
const { searchForm } = toRefs(data);
const {form, rules} = toRefs(data);
const {searchForm} = toRefs(data);
// 查询列表
const handleQuery = () => {
@@ -328,15 +268,15 @@
const getList = () => {
  tableLoading.value = true;
  deliveryLedgerListPage({ ...searchForm.value, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.data.records || [];
      total.value = res.data.total || 0;
    })
    .catch(() => {
      tableLoading.value = false;
    });
  deliveryLedgerListPage({...searchForm.value, ...page})
      .then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records || [];
        total.value = res.data.total || 0;
      })
      .catch(() => {
        tableLoading.value = false;
      });
};
// 销售订单变化时自动填充客户名称
@@ -359,10 +299,9 @@
    proxy.$modal.msgWarning("只有审核通过的数据才可以补充发货信息");
    return;
  }
  operationType.value = type;
  const baseUrl = import.meta.env.VITE_APP_BASE_API;
  if (type === 'edit' && row) {
    form.value = {
      id: row.id ?? null,
@@ -374,43 +313,9 @@
      expressCompany: row.expressCompany ?? "",
      expressNumber: row.expressNumber ?? "",
    };
    // 如果有图片,将 commonFileList 转换为文件列表格式
    if (row.commonFileList && Array.isArray(row.commonFileList) && row.commonFileList.length > 0) {
      deliveryFileList.value = row.commonFileList.map((file, index) => {
        const fileUrl = normalizeFileUrl(file.url || '');
        return {
          uid: file.id || Date.now() + index,
          name: file.name || `image_${index + 1}.jpg`,
          url: fileUrl,
          status: 'success',
          response: {
            code: 200,
            data: {
              tempId: file.id,
              url: fileUrl
            }
          },
          tempId: file.id // 保存文件ID,用于提交时使用
        };
      });
    } else {
      deliveryFileList.value = [];
    }
  } else {
    form.value = {
      id: null,
      salesContractNo: "",
      customerName: "",
      type: "货车",
      shippingDate: getCurrentDate(),
      shippingCarNumber: "",
      expressCompany: "",
      expressNumber: "",
    };
    deliveryFileList.value = [];
    deliveryFileList.value = row.storageBlobVOS || [];
  }
  dialogFormVisible.value = true;
};
@@ -418,25 +323,17 @@
const openDetail = (row) => {
  detailRow.value = row || null;
  const list = Array.isArray(row?.commonFileList) ? row.commonFileList : [];
  detailImages.value = list
    .map((f) => ({ url: normalizeFileUrl(f?.url || '') }))
    .filter((i) => !!i.url);
  detailDialogVisible.value = true;
};
const closeDetail = () => {
  detailDialogVisible.value = false;
  detailRow.value = null;
  detailImages.value = [];
};
// 提交表单
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      let tempFileIds = [];
      if (deliveryFileList.value !== null && deliveryFileList.value.length > 0) {
        tempFileIds = deliveryFileList.value.map((item) => item.tempId);
      }
      const payload = {
        id: form.value.id,
        type: form.value.type,
@@ -444,9 +341,9 @@
        shippingCarNumber: form.value.type === "货车" ? form.value.shippingCarNumber : "",
        expressCompany: form.value.type === "快递" ? form.value.expressCompany : "",
        expressNumber: form.value.type === "快递" ? form.value.expressNumber : "",
        tempFileIds: tempFileIds,
        storageBlobDTOs: deliveryFileList.value || [],
      };
            deductStock(payload).then((res) => {
      deductStock(payload).then((res) => {
        proxy.$modal.msgSuccess("操作成功");
        closeDia();
        getList();
@@ -469,12 +366,12 @@
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/shippingInfo/export", {}, "发货台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
      .then(() => {
        proxy.download("/shippingInfo/export", {}, "发货台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// 批量删除
@@ -483,29 +380,29 @@
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  // 检查选中的行是否有"审核中"状态
  const approvingRows = selectedRows.value.filter(row => isApproving(row.status));
  if (approvingRows.length > 0) {
    proxy.$modal.msgWarning("审核中的数据不能删除");
    return;
  }
  const ids = selectedRows.value.map((item) => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      delDeliveryLedger(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      .then(() => {
        delDeliveryLedger(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 单个删除
@@ -515,21 +412,21 @@
    proxy.$modal.msgWarning("审核中的数据不能删除");
    return;
  }
  ElMessageBox.confirm("此操作将删除该记录,是否确认?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      delDeliveryLedger([row.id]).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      .then(() => {
        delDeliveryLedger([row.id]).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 发货类型校验:货车时要求车牌,快递时要求快递公司
@@ -563,11 +460,13 @@
  proxy.$modal.loading("正在上传图片,请稍候...");
  return true;
}
// 发货图片上传失败
function handleDeliveryUploadError(err) {
  proxy.$modal.msgError("上传图片失败");
  proxy.$modal.closeLoading();
}
// 发货图片上传成功回调
function handleDeliveryUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
@@ -579,6 +478,7 @@
    proxy.$refs.deliveryFileUpload.handleRemove(file);
  }
}
// 移除发货图片
function handleDeliveryRemove(file) {
  console.log('file--', file)
@@ -727,17 +627,21 @@
    display: none;
  }
}
.detail-wrapper {
  padding: 8px 0;
}
.detail-images {
  margin-top: 16px;
}
.detail-images-title {
  font-weight: 600;
  margin-bottom: 10px;
  color: #303133;
}
.detail-image {
  width: 120px;
  height: 120px;
@@ -745,6 +649,7 @@
  margin-bottom: 10px;
  border-radius: 6px;
}
.detail-images-empty {
  margin-top: 16px;
  color: #909399;