src/views/salesManagement/deliveryLedger/index.vue
@@ -6,12 +6,8 @@
          <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-form-item>
        <el-form-item label="快递单号:">
          <el-input v-model="searchForm.expressNumber" placeholder="请输入" clearable prefix-icon="Search" style="width: 200px"
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search" style="width: 200px"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item>
@@ -32,100 +28,265 @@
        <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="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 fixed="right" label="操作" width="150" align="center">
        <el-table-column label="发货进度" align="center" width="150">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button link type="danger" size="small" @click="handleDeleteSingle(scope.row)">删除</el-button>
            <el-progress
              :percentage="getShippingProgress(scope.row)"
              :color="getProgressColor(scope.row)"
              :stroke-width="8"
            />
          </template>
        </el-table-column>
        <el-table-column label="审核状态" prop="status" align="center" width="120">
          <template #default="scope">
            <el-tag :type="getApprovalStatusType(scope.row.status)">
              {{ getApprovalStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="200" align="center">
          <template #default="scope">
            <el-button
              link
              type="primary"
              size="small"
              :disabled="(scope.row.waitShippingTotal || 0) <= 0"
              @click="openForm('edit', scope.row)">分批发货</el-button>
            <el-button
              link
              type="primary"
              size="small"
              @click="openDetail(scope.row)"
            >详情</el-button>
            <el-button
              link
              type="danger"
              size="small"
              :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" />
    </div>
    <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="请选择发货类型"
    <!-- 发货详情/分批发货弹窗 -->
    <el-dialog
      v-model="dialogFormVisible"
      :title="dialogMode === 'edit' ? '分批发货管理' : '发货台账详情'"
      width="90%"
      @close="closeDia"
      class="batch-shipping-dialog"
    >
      <div v-if="currentShippingOrder" class="shipping-dialog-container">
        <el-row :gutter="24">
          <el-col :span="7">
            <el-card shadow="never" class="order-info-card">
              <template #header>
                <div class="card-header">
                  <span>订单信息</span>
                  <el-tag :type="getProgressColor(currentShippingOrder) === '#67C23A' ? 'success' : 'warning'" effect="dark">
                    {{ getShippingProgress(currentShippingOrder) }}%
                  </el-tag>
                </div>
              </template>
              <div class="order-info-content">
                <div class="info-item">
                  <span class="label">销售订单</span>
                  <span class="value">{{ currentShippingOrder.salesContractNo || '--' }}</span>
                </div>
                <div class="info-item">
                  <span class="label">客户名称</span>
                  <span class="value">{{ currentShippingOrder.customerName || '--' }}</span>
                </div>
                <div class="info-item">
                  <span class="label">发货订单号</span>
                  <span class="value">{{ currentShippingOrder.shippingNo || '--' }}</span>
                </div>
                <el-divider />
                <div class="quantity-summary">
                  <div class="summary-item">
                    <div class="summary-label">总发货数量</div>
                    <div class="summary-value total">{{ currentShippingOrder.shippingTotal || 0 }}</div>
                  </div>
                  <div class="summary-item">
                    <div class="summary-label">已发货数量</div>
                    <div class="summary-value shipped">{{ currentShippingOrder.shippingSuccessTotal || 0 }}</div>
                  </div>
                  <div class="summary-item">
                    <div class="summary-label">待发货数量</div>
                    <div class="summary-value waiting">{{ currentShippingOrder.waitShippingTotal || 0 }}</div>
                  </div>
                </div>
                <div class="progress-wrapper">
                  <el-progress
                    :percentage="getShippingProgress(currentShippingOrder)"
                    :color="getProgressColor(currentShippingOrder)"
                    :stroke-width="12"
                    :show-text="false"
                  />
                </div>
              </div>
              <div class="action-buttons" v-if="dialogMode === 'edit'">
                <el-button type="primary" size="large" @click="showAddShippingForm" :disabled="!canAddShipping()" style="width: 100%">
                  <el-icon><Plus /></el-icon> 新增发货
                </el-button>
              </div>
            </el-card>
          </el-col>
          <el-col :span="17">
            <el-card shadow="never" class="shipping-records-card">
              <template #header>
                <div class="card-header">
                  <span>发货记录</span>
                  <div class="header-actions">
                    <span class="record-count">共 {{ shippingRecords.length }} 条记录</span>
                  </div>
                </div>
              </template>
              <div v-if="shippingRecords.length === 0" class="empty-state">
                <el-empty description="暂无发货记录" />
              </div>
              <div v-else class="shipping-records-list">
                <div v-for="(record, index) in shippingRecords" :key="record.id || index" class="shipping-record-item">
                  <div class="record-header">
                    <div class="record-title">
                      <el-tag :type="record.type === '货车' ? 'primary' : 'success'" size="small">
                        {{ record.type }}
                      </el-tag>
                    </div>
                    <div class="record-date">{{ record.shippingDate }}</div>
                  </div>
                  <div class="record-body">
                    <div class="record-info">
                      <div class="info-row">
                        <span class="info-label">发货数量</span>
                        <span class="info-value quantity">{{ record.shippingNum }}</span>
                      </div>
                      <div class="info-row" v-if="record.type === '货车'">
                        <span class="info-label">车牌号</span>
                        <span class="info-value">{{ record.shippingCarNumber || '--' }}</span>
                      </div>
                      <div class="info-row" v-else>
                        <span class="info-label">快递公司</span>
                        <span class="info-value">{{ record.expressCompany || '--' }}</span>
                      </div>
                      <div class="info-row" v-if="record.type === '快递'">
                        <span class="info-label">快递单号</span>
                        <span class="info-value">{{ record.expressNumber || '--' }}</span>
                      </div>
                      <div class="info-row" v-if="record.commonFileList && record.commonFileList.length > 0">
                        <span class="info-label">发货图片</span>
                        <div class="record-images">
                          <el-image
                            v-for="(file, imgIndex) in record.commonFileList"
                            :key="imgIndex"
                            :src="normalizeFileUrl(file?.url)"
                            :preview-src-list="record.commonFileList.map(f => normalizeFileUrl(f?.url))"
                            fit="cover"
                            class="record-image"
                            preview-teleported
                          />
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </el-card>
          </el-col>
        </el-row>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">关闭</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 新增发货记录弹窗 -->
    <el-dialog v-model="showAddForm" title="新增发货记录" width="600px" @close="hideAddShippingForm">
      <el-alert
        :title="`可发货数量:${getMaxShippingQuantity()}`"
        type="info"
        :closable="false"
        show-icon
        style="margin-bottom: 20px"
      />
      <el-form :model="shippingForm" label-width="100px" :rules="shippingRules" ref="shippingFormRef">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="发货数量" prop="shippingNum">
              <el-input-number
                v-model="shippingForm.shippingNum"
                :min="1"
                :max="getMaxShippingQuantity()"
                :precision="0"
                placeholder="请输入发货数量"
                style="width: 100%"
                @change="handleShippingTypeChange"
              >
                <el-option label="货车" value="货车" />
                <el-option label="快递" value="快递" />
              </el-select>
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发货类型" prop="type">
              <el-radio-group v-model="shippingForm.type">
                <el-radio label="货车">货车</el-radio>
                <el-radio label="快递">快递</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="发货日期:" prop="shippingDate">
              <el-date-picker
            <el-form-item label="发货日期" prop="shippingDate">
              <el-date-picker
                v-model="shippingForm.shippingDate"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="datetime"
                placeholder="请选择"
                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>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24" v-if="form.type === '货车'">
            <el-form-item label="发货车牌号:" prop="shippingCarNumber">
              <el-input
                v-model="form.shippingCarNumber"
                placeholder="请输入发货车牌号"
                clearable
              />
        <el-row :gutter="20">
          <el-col :span="24" v-if="shippingForm.type === '货车'">
            <el-form-item label="发货车牌号" prop="shippingCarNumber">
              <el-input v-model="shippingForm.shippingCarNumber" placeholder="请输入发货车牌号" />
            </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
              />
            <el-form-item label="快递公司" prop="expressCompany">
              <el-input v-model="shippingForm.expressCompany" placeholder="请输入快递公司" />
            </el-form-item>
          </el-col>
          <el-col :span="24" v-if="shippingForm.type === '快递'">
            <el-form-item label="快递单号" prop="expressNumber">
              <el-input v-model="shippingForm.expressNumber" placeholder="请输入快递单号" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30" v-if="form.type === '快递'">
        <el-row>
          <el-col :span="24">
            <el-form-item label="快递单号:" prop="expressNumber">
              <el-input
                v-model="form.expressNumber"
                placeholder="请输入快递单号"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="发货图片:">
            <el-form-item label="发货图片">
              <el-upload 
                v-model:file-list="deliveryFileList"
                v-model:file-list="shippingFileList"
                :action="upload.url" 
                multiple 
                ref="deliveryFileUpload"
                ref="shippingFileUpload"
                auto-upload
                :headers="upload.headers" 
                :data="{ type: 9 }"
                :before-upload="handleDeliveryBeforeUpload" 
                :on-error="handleDeliveryUploadError"
                :on-success="handleDeliveryUploadSuccess" 
                :on-remove="handleDeliveryRemove"
                :on-remove="handleShippingRemove"
                list-type="picture-card"
                :limit="9"
                accept="image/png,image/jpeg,image/jpg"
@@ -133,7 +294,7 @@
                <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
                <template #tip>
                  <div class="el-upload__tip">
                    支持 jpg、jpeg、png 格式,最多上传 9 张,单张大小不超过 10MB
                    支持 jpg、jpeg、png 格式,最多上传 9 张
                  </div>
                </template>
              </el-upload>
@@ -143,8 +304,8 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
          <el-button type="primary" @click="submitShipping" :loading="shippingRecordsLoading">确认发货</el-button>
          <el-button @click="hideAddShippingForm">取消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -159,9 +320,13 @@
import { getToken } from "@/utils/auth";
import { getCurrentDate } from "@/utils/index.js";
import {
  deliveryLedgerListPage,
  addOrUpdateDeliveryLedger,
  delDeliveryLedger,
   deliveryLedgerListPage,
   addOrUpdateDeliveryLedger,
   delDeliveryLedger, deductStock,
   shippingInfoDetailListPage,
   addShippingInfoDetail,
   updateShippingInfoDetail,
   delShippingInfoDetail,
} from "@/api/salesManagement/deliveryLedger.js";
import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
 
@@ -170,61 +335,75 @@
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const salesOrderOptions = ref([]);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const deliveryFileList = ref([]);
const javaApi = proxy.javaApi;
// 发货详情/分批发货弹框
const dialogFormVisible = ref(false);
const dialogMode = ref('view');
const currentShippingOrder = ref(null);
const shippingRecords = ref([]);
const shippingRecordsLoading = ref(false);
const showAddForm = ref(false);
const shippingForm = ref({
  shippingNum: null,
  type: "货车",
  shippingDate: getCurrentDate(),
  shippingCarNumber: "",
  expressCompany: "",
  expressNumber: "",
});
const shippingRules = {
  shippingNum: [{ required: true, message: "请输入发货数量", trigger: "blur" }],
  type: [{ required: true, message: "请选择发货类型", trigger: "change" }],
  shippingDate: [{ required: true, message: "请选择发货日期", trigger: "change" }],
  shippingCarNumber: [
    { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" }
  ],
  expressCompany: [
    { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" }
  ],
};
const shippingFileList = ref([]);
const normalizeFileUrl = (rawUrl = '') => {
  let fileUrl = rawUrl || '';
  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("");
const dialogFormVisible = ref(false);
// 搜索表单数据
const data = reactive({
  searchForm: {
    salesContractNo: "", // 销售订单号
    shippingCarNumber: "", // 车牌号
    expressNumber: "", // 快递单号
  },
  form: {
    id: null,
    salesContractNo: "",
    customerName: "",
    type: "货车", // 货车, 快递
    shippingDate: "",
    shippingCarNumber: "",
    expressCompany: "",
    expressNumber: "", // 快递单号
  },
  rules: {
    salesContractNo: [{ required: true, message: "请选择销售订单", trigger: "change" }],
    customerName: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
    type: [
      { required: true, message: "请选择发货类型", trigger: "change" }
    ],
    shippingDate: [{ required: true, message: "请选择发货时间", trigger: "change" }],
    shippingCarNumber: [
      { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" }
    ],
    expressCompany: [
      { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" }
    ],
  },
});
const { form, rules } = toRefs(data);
const { searchForm } = toRefs(data);
// 查询列表
const handleQuery = () => {
@@ -251,137 +430,155 @@
    });
};
// 销售订单变化时自动填充客户名称
const handleSalesOrderChange = (value) => {
  const selectedOrder = salesOrderOptions.value.find(item => item.salesContractNo === value);
  if (selectedOrder) {
    form.value.customerName = selectedOrder.customerName;
  }
};
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// 打开弹框
// 打开发货弹框
const openForm = async (type, row) => {
  operationType.value = type;
  const baseUrl = import.meta.env.VITE_APP_BASE_API;
  if (type === 'edit' && row) {
    form.value = {
      id: row.id ?? null,
      salesContractNo: row.salesContractNo ?? "",
      customerName: row.customerName ?? "",
      type: row.type || "货车",
      shippingDate: row.shippingDate || getCurrentDate(),
      shippingCarNumber: row.shippingCarNumber ?? "",
      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) => {
        // 处理 URL:将 Windows 路径转换为可访问的 URL
        let fileUrl = file.url || '';
        console.log('原始 URL:', fileUrl);
        // 如果 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;
            console.log('转换后的相对路径:', fileUrl);
          } else {
            // 如果没有找到 uploads,提取最后一个目录和文件名
            const parts = fileUrl.split('\\');
            const fileName = parts[parts.length - 1];
            fileUrl = '/uploads/' + fileName;
            console.log('未找到 uploads,使用文件名:', fileUrl);
          }
        }
        // 确保所有非 http 开头的 URL 都拼接 baseUrl
        if (fileUrl && !fileUrl.startsWith('http')) {
          // 确保路径以 / 开头
          if (!fileUrl.startsWith('/')) {
            fileUrl = '/' + fileUrl;
          }
          // 拼接 baseUrl
          fileUrl = javaApi + fileUrl;
          console.log('最终拼接的 URL:', fileUrl);
        }
        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 = [];
  if (type === 'edit' && row && (row.waitShippingTotal || 0) <= 0) {
    proxy.$modal.msgWarning("没有待发货数量,无法分批发货");
    return;
  }
  
  dialogMode.value = type === 'edit' ? 'edit' : 'view';
  currentShippingOrder.value = row;
  showAddForm.value = false;
  shippingForm.value = {
    shippingNum: null,
    type: "货车",
    shippingDate: getCurrentDate(),
    shippingCarNumber: "",
    expressCompany: "",
    expressNumber: "",
  };
  shippingFileList.value = [];
  await loadShippingRecords(row.id);
  dialogFormVisible.value = true;
};
// 提交表单
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
// 打开详情弹框
const openDetail = (row) => {
  openForm('view', row);
};
// 加载发货记录
const loadShippingRecords = async (shippingInfoId) => {
  shippingRecordsLoading.value = true;
  try {
    const res = await shippingInfoDetailListPage({ shippingInfoId, current: 1, size: 100 });
    shippingRecords.value = res.data.records || [];
  } catch (error) {
    shippingRecords.value = [];
  } finally {
    shippingRecordsLoading.value = false;
  }
};
// 显示新增发货表单
const showAddShippingForm = () => {
  showAddForm.value = true;
  shippingForm.value = {
    shippingNum: null,
    type: "货车",
    shippingDate: getCurrentDate(),
    shippingCarNumber: "",
    expressCompany: "",
    expressNumber: "",
  };
  shippingFileList.value = [];
};
// 隐藏新增发货表单
const hideAddShippingForm = () => {
  showAddForm.value = false;
  shippingForm.value = {
    shippingNum: null,
    type: "货车",
    shippingDate: getCurrentDate(),
    shippingCarNumber: "",
    expressCompany: "",
    expressNumber: "",
  };
  shippingFileList.value = [];
};
// 获取最大可发货数量
const getMaxShippingQuantity = () => {
  if (!currentShippingOrder.value) return 0;
  const waitShipping = currentShippingOrder.value.waitShippingTotal || 0;
  return Math.max(0, waitShipping);
};
// 是否可以新增发货
const canAddShipping = () => {
  if (!currentShippingOrder.value) return false;
  const waitShipping = currentShippingOrder.value.waitShippingTotal || 0;
  return waitShipping > 0;
};
// 提交发货
const submitShipping = () => {
  proxy.$refs["shippingFormRef"].validate((valid) => {
    if (valid) {
      let tempFileIds = [];
      if (deliveryFileList.value !== null && deliveryFileList.value.length > 0) {
        tempFileIds = deliveryFileList.value.map((item) => item.tempId);
      if (shippingFileList.value !== null && shippingFileList.value.length > 0) {
        tempFileIds = shippingFileList.value.map((item) => item.tempId);
      }
      const payload = {
        id: form.value.id,
        type: form.value.type,
        shippingDate: form.value.shippingDate,
        shippingCarNumber: form.value.type === "货车" ? form.value.shippingCarNumber : "",
        expressCompany: form.value.type === "快递" ? form.value.expressCompany : "",
        expressNumber: form.value.type === "快递" ? form.value.expressNumber : "",
        shippingInfoId: currentShippingOrder.value.id,
        salesLedgerId: currentShippingOrder.value.salesLedgerId,
        salesLedgerProductId: currentShippingOrder.value.salesLedgerProductId,
        shippingTotal: currentShippingOrder.value.shippingTotal,
        shippingNum: shippingForm.value.shippingNum,
        type: shippingForm.value.type,
        shippingDate: shippingForm.value.shippingDate,
        shippingCarNumber: shippingForm.value.type === "货车" ? shippingForm.value.shippingCarNumber : "",
        expressCompany: shippingForm.value.type === "快递" ? shippingForm.value.expressCompany : "",
        expressNumber: shippingForm.value.type === "快递" ? shippingForm.value.expressNumber : "",
        tempFileIds: tempFileIds,
      };
      addOrUpdateDeliveryLedger(payload).then((res) => {
        proxy.$modal.msgSuccess("操作成功");
        closeDia();
      addShippingInfoDetail(payload).then((res) => {
        proxy.$modal.msgSuccess("发货成功");
        hideAddShippingForm();
        loadShippingRecords(currentShippingOrder.value.id);
        getList();
      });
    }
  });
};
// 删除发货记录
const deleteShippingRecord = (row) => {
  if (isApproving(row.status)) {
    proxy.$modal.msgWarning("审核中的数据不能删除");
    return;
  }
  ElMessageBox.confirm("此操作将删除该发货记录,是否确认?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      delShippingInfoDetail([row.id]).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        loadShippingRecords(currentShippingOrder.value.id);
        getList();
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 关闭弹框
const closeDia = () => {
  proxy.resetForm("formRef");
  deliveryFileList.value = []; // 清空文件列表
  dialogFormVisible.value = false;
  currentShippingOrder.value = null;
  shippingRecords.value = [];
  showAddForm.value = false;
};
// 导出
@@ -401,13 +598,18 @@
// 批量删除
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
  if (selectedRows.value.length === 0) {
    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: "取消",
@@ -426,6 +628,11 @@
// 单个删除
const handleDeleteSingle = (row) => {
  if (isApproving(row.status)) {
    proxy.$modal.msgWarning("审核中的数据不能删除");
    return;
  }
  ElMessageBox.confirm("此操作将删除该记录,是否确认?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
@@ -442,15 +649,15 @@
    });
};
// 发货类型校验:货车时要求车牌,快递时要求快递公司
// 发货类型校验
const validateShippingCarNumber = (value, callback) => {
  if (form.value.type === "货车") {
  if (shippingForm.value.type === "货车") {
    if (!value) return callback(new Error("请输入发货车牌号"));
  }
  callback();
};
const validateExpressCompany = (value, callback) => {
  if (form.value.type === "快递") {
  if (shippingForm.value.type === "快递") {
    if (!value) return callback(new Error("请输入快递公司"));
  }
  callback();
@@ -458,13 +665,11 @@
// 发货图片上传前校检
function handleDeliveryBeforeUpload(file) {
  // 校检文件类型
  const isImage = file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg';
  if (!isImage) {
    proxy.$modal.msgError("只能上传 jpg、jpeg、png 格式的图片!");
    return false;
  }
  // 校检文件大小
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isLt10M) {
    proxy.$modal.msgError("上传图片大小不能超过 10MB!");
@@ -473,11 +678,13 @@
  proxy.$modal.loading("正在上传图片,请稍候...");
  return true;
}
// 发货图片上传失败
function handleDeliveryUploadError(err) {
  proxy.$modal.msgError("上传图片失败");
  proxy.$modal.closeLoading();
}
// 发货图片上传成功回调
function handleDeliveryUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
@@ -486,43 +693,112 @@
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.deliveryFileUpload.handleRemove(file);
  }
}
// 移除发货图片
function handleDeliveryRemove(file) {
  console.log('file--', file)
  // 如果是编辑模式且文件有 id,需要调用接口删除
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.uid);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      // 从文件列表中移除
      const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
      if (index > -1) {
        deliveryFileList.value.splice(index, 1);
      }
    }).catch(() => {
      proxy.$modal.msgError("删除失败");
    });
  } else {
    // 新增模式或没有 id 的文件,直接从列表中移除
    const index = deliveryFileList.value.findIndex(item => item.uid === file.uid);
    if (index > -1) {
      deliveryFileList.value.splice(index, 1);
    }
    proxy.$refs.shippingFileUpload.handleRemove(file);
  }
}
// 发货类型切换时清空对应字段
const handleShippingTypeChange = (val) => {
  if (val === "货车") {
    form.value.expressCompany = "";
    form.value.expressNumber = "";
  } else {
    form.value.shippingCarNumber = "";
// 移除发货图片
function handleShippingRemove(file) {
  if (file.uid) {
    delLedgerFile([file.uid]).then(() => {
      proxy.$modal.msgSuccess("删除成功");
    });
  }
}
const getShippingProgress = (row) => {
  const shipped = row.shippingSuccessTotal || 0;
  const total = shipped + (row.waitShippingTotal || 0);
  if (total === 0) return 0;
  return Math.round((shipped / total) * 100);
};
// 获取进度条颜色
const getProgressColor = (row) => {
  const progress = getShippingProgress(row);
  if (progress === 100) return '#67C23A';
  if (progress >= 50) return '#E6A23C';
  return '#409EFF';
};
// 获取审核状态文本
const getApprovalStatusText = (status) => {
  if (status === null || status === undefined || status === '') {
    return '待审核';
  }
  if (typeof status === 'number') {
    const statusMap = {
      0: '待审核',
      1: '审核中',
      2: '审核拒绝',
      3: '审核通过'
    };
    return statusMap[status] || '待审核';
  }
  const statusStr = String(status).trim();
  const statusTextMap = {
    '待审核': '待审核',
    '审核中': '审核中',
    '审核拒绝': '审核拒绝',
    '审核通过': '审核通过',
    '0': '待审核',
    '1': '审核中',
    '2': '审核拒绝',
    '3': '审核通过'
  };
  return statusTextMap[statusStr] || statusStr || '待审核';
};
// 获取审核状态标签类型
const getApprovalStatusType = (status) => {
  if (status === null || status === undefined || status === '') {
    return 'info';
  }
  if (typeof status === 'number') {
    const typeMap = {
      0: 'info',
      1: 'warning',
      2: 'danger',
      3: 'success'
    };
    return typeMap[status] || 'info';
  }
  const statusStr = String(status).trim();
  const typeTextMap = {
    '待审核': 'info',
    '审核中': 'warning',
    '审核拒绝': 'danger',
    '审核通过': 'success',
    '0': 'info',
    '1': 'warning',
    '2': 'danger',
    '3': 'success'
  };
  return typeTextMap[statusStr] || 'info';
};
// 检查审核状态是否为"审核通过"
const isApproved = (status) => {
  if (status === null || status === undefined || status === '') {
    return false;
  }
  if (typeof status === 'number') {
    return status === 3;
  }
  const statusStr = String(status).trim();
  return statusStr === '审核通过' || statusStr === '3';
};
// 检查审核状态是否为"审核中"
const isApproving = (status) => {
  if (status === null || status === undefined || status === '') {
    return false;
  }
  if (typeof status === 'number') {
    return status === 1;
  }
  const statusStr = String(status).trim();
  return statusStr === '审核中' || statusStr === '1';
};
onMounted(() => {
@@ -541,11 +817,228 @@
  margin-bottom: 10px;
}
// 隐藏图片上传组件的预览按钮(放大镜)
:deep(.el-upload-list--picture-card .el-upload-list__item-actions) {
  .el-upload-list__item-preview {
    display: none;
  }
}
</style>
.batch-shipping-dialog {
  .shipping-dialog-container {
    min-height: 500px;
  }
  .order-info-card {
    height: 100%;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-weight: bold;
    }
    .order-info-content {
      .info-item {
        display: flex;
        justify-content: space-between;
        padding: 8px 0;
        .label {
          color: #606266;
          font-size: 14px;
        }
        .value {
          color: #303133;
          font-weight: 500;
          font-size: 14px;
        }
      }
      .quantity-summary {
        display: flex;
        justify-content: space-between;
        margin: 16px 0;
        .summary-item {
          text-align: center;
          flex: 1;
          .summary-label {
            font-size: 12px;
            color: #909399;
            margin-bottom: 8px;
          }
          .summary-value {
            font-size: 20px;
            font-weight: bold;
            &.total {
              color: #409EFF;
            }
            &.shipped {
              color: #67C23A;
            }
            &.waiting {
              color: #E6A23C;
            }
          }
        }
      }
      .progress-wrapper {
        margin-top: 16px;
      }
    }
    .action-buttons {
      margin-top: 20px;
    }
  }
  .shipping-records-card {
    height: 100%;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-weight: bold;
      .header-actions {
        display: flex;
        align-items: center;
        gap: 12px;
        .record-count {
          font-size: 12px;
          color: #909399;
          font-weight: normal;
        }
      }
      .record-count {
        font-size: 12px;
        color: #909399;
        font-weight: normal;
      }
    }
    .empty-state {
      padding: 40px 0;
    }
    .shipping-records-list {
      .shipping-record-item {
        border: 1px solid #EBEEF5;
        border-radius: 4px;
        padding: 16px;
        margin-bottom: 12px;
        transition: all 0.3s;
        &:hover {
          border-color: #409EFF;
          box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        }
        &:last-child {
          margin-bottom: 0;
        }
        .record-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 12px;
          padding-bottom: 12px;
          border-bottom: 1px solid #F5F7FA;
          .record-title {
            display: flex;
            align-items: center;
            gap: 8px;
          }
          .record-date {
            font-size: 12px;
            color: #909399;
          }
        }
        .record-body {
          .record-info {
            display: flex;
            flex-wrap: wrap;
            gap: 24px;
            .info-row {
              display: flex;
              align-items: center;
              gap: 8px;
              .info-label {
                font-size: 12px;
                color: #909399;
              }
              .info-value {
                font-size: 14px;
                color: #303133;
                &.quantity {
                  font-weight: bold;
                  color: #409EFF;
                  font-size: 16px;
                }
              }
            }
            .record-images {
              display: flex;
              gap: 8px;
              flex-wrap: wrap;
              .record-image {
                width: 60px;
                height: 60px;
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.3s;
                &:hover {
                  transform: scale(1.05);
                }
              }
            }
          }
        }
      }
    }
  }
}
.shipping-detail-wrapper {
  .detail-images {
    margin-top: 20px;
    .detail-images-title {
      font-size: 14px;
      font-weight: bold;
      margin-bottom: 12px;
    }
    .detail-image {
      width: 120px;
      height: 120px;
      margin-right: 12px;
      margin-bottom: 12px;
      border-radius: 4px;
      cursor: pointer;
    }
  }
}
</style>