peng
2025-10-30 914b2943c09f947ab59e8143e25f4d5e23b8dcf0
seller/src/views/order/order/orderDetail.vue
@@ -17,6 +17,10 @@
        <Button @click="toPrint" type="primary" ghost
          v-if="allowOperation.ship && logisticsType != 'SHUNFENG'">打印电子面单</Button>
        <Button @click="modifyRemark" type="primary">添加备注</Button>
        <!-- 将编辑模板按钮移到这里 -->
        <Button v-if="orderInfo.userCheckTemplates && orderInfo.userCheckTemplates.length > 0"
                @click="editTemplateInfo(orderInfo.userCheckTemplates[0].templateId, sn)"
                type="primary">编辑模板</Button>
      </div>
    </Card>
@@ -146,7 +150,63 @@
            }}
          </div>
        </div>
        </Col>
        <Col span="24">
          <!-- 外层容器:循环遍历 userCheckTemplates 集合 -->
          <div class="check-template-list">
              <div class="template-item" v-for="(item, index) in orderInfo.userCheckTemplates" :key="item.id">
                <!-- 2. 模板标题:仅第一个循环项显示 templateName(index===0 控制) -->
                <div class="div-item" > <!-- 关键:仅首项渲染 -->
                  <div class="div-item-left">商品模板:</div>
                  <div class="div-item-right">
                    {{ item.templateName || '无模板标题' }} <!-- 处理空值默认显示 -->
                  </div>
                </div>
                <!-- 4. 选择图片:渲染 chooseImg 字段(处理 null/空值) -->
                <div class="div-item">
                  <div class="div-item-left">模板图片:</div>
                  <div class="div-item-right">
                    <img
                      v-if="item.chooseImg"
                      :src="getImageUrl(item.chooseImg)"
                      alt="选择的图片"
                      class="selected-img"
                      style="max-width: 200px; max-height: 150px;"
                    >
                    <span v-else>无选择图片</span> <!-- 无图片时默认文本 -->
                  </div>
                </div>
            <!-- 1. 商品模板:每个循环项都显示 subName -->
            <div class="div-item">
              <div class="div-item-left">模板标题:</div>
              <div class="div-item-right">
                {{ item.subName || '无商品模板名称' }} <!-- 处理空值默认显示 -->
              </div>
            </div>
            <!-- 3. 文本内容:判断 content 是「图片URL」还是「纯文本」 -->
            <div class="div-item">
              <div class="div-item-left">{{isUrl(item.content)? '图片:':'文本内容'}}</div>
              <div class="div-item-right">
                <!-- 正则判断:content 以 http/https 开头 → 渲染图片;否则渲染文本 -->
                <img
                  v-if="isUrl(item.content)"
                  :src="getImageUrl(item.content)"
                  alt="内容图片"
                  class="content-img"
                  style="max-width: 200px; max-height: 150px;"
                >
                <span v-else>{{ item.content || '无文本内容' }}</span> <!-- 纯文本/空值处理 -->
              </div>
            </div>
            <!-- 可选:循环项分隔线,优化视觉 -->
            <hr v-if="index !== orderInfo.userCheckTemplates.length - 1" style="margin: 15px 0; border: none; border-top: 1px solid #eee;">
          </div>
          </div>
        </Col>
      </Row>
    </Card>
@@ -379,6 +439,10 @@
      <div v-if="packageTraceList.length > 0" v-for="(packageItem, packageIndex) in packageTraceList" :key="packageIndex">
        <div class="layui-layer-wrap">
          <dl>
            <dt>包裹编号:</dt>
            <dd><div class="text-box">{{ packageItem.packageNo }}</div></dd>
          </dl>
          <dl>
            <dt>物流公司:</dt>
            <dd><div class="text-box">{{ packageItem.logisticsName }}</div></dd>
          </dl>
@@ -404,6 +468,10 @@
            </ul>
            <ul class="express-log" v-else><li>暂无物流信息</li></ul>
          </div>
          <!-- 添加修改物流单号按钮 -->
          <div style="text-align: center; margin-top: 10px;">
            <Button type="primary" @click="modifyPackageLogistics(packageItem)">修改物流单号</Button>
          </div>
        </div>
      </div>
      <div v-if = "packageTraceList.length == 0 && logisticsInfo">
@@ -425,6 +493,10 @@
            </ul>
            <ul class="express-log" v-else><li>暂无物流信息</li></ul>
          </div>
          <!-- 添加修改物流单号按钮 -->
          <div style="text-align: center; margin-top: 10px;">
            <Button type="primary" @click="modifySingleLogistics">修改物流单号</Button>
          </div>
        </div>
      </div>
      <div slot="footer" style="text-align: right">
@@ -432,7 +504,38 @@
      </div>
    </Modal>
    <!-- 修改物流单号弹窗 -->
    <Modal v-model="modifyLogisticsModal" width="530">
      <p slot="header">
        <Icon type="edit"></Icon>
        <span>修改物流单号</span>
      </p>
      <div>
        <Form ref="modifyLogisticsForm" :model="modifyLogisticsForm" label-position="left" :label-width="100" :rules="modifyLogisticsValidate">
          <FormItem label="包裹编号" prop="packageNo">
            <Input v-model="modifyLogisticsForm.packageNo" size="large" disabled></Input>
          </FormItem>
          <FormItem label="订单编号" prop="orderSn">
            <Input v-model="modifyLogisticsForm.orderSn" size="large" disabled></Input>
          </FormItem>
          <FormItem label="快递公司" prop="logisticsId">
            <Select v-model="modifyLogisticsForm.logisticsId" placeholder="请选择快递公司" style="width: 100%">
              <Option v-for="(item, i) in checkedLogistics" :key="i" :value="item.logisticsId">{{ item.name }}</Option>
            </Select>
          </FormItem>
          <FormItem label="快递单号" prop="logisticsNo">
            <Input v-model="modifyLogisticsForm.logisticsNo" size="large"></Input>
          </FormItem>
<!--          <FormItem label="快递编码" prop="logisticsCode">-->
<!--            <Input v-model="modifyLogisticsForm.logisticsCode" size="large"></Input>-->
<!--          </FormItem>-->
        </Form>
      </div>
      <div slot="footer" style="text-align: right">
        <Button @click="modifyLogisticsModal = false">关闭</Button>
        <Button type="primary" @click="modifyLogisticsSubmit">确认修改</Button>
      </div>
    </Modal>
    <!-- 订单发货 -->
    <Modal v-model="orderDeliverModal" width="500px">
      <p slot="header">
@@ -527,7 +630,7 @@
      </div>
    </Modal>
    <!--订单分包裹发货-->
    <Modal v-model="groupShipModal" :loading="shipLoading" title="分包裹发快递" width="1000">
    <Modal v-model="groupShipModal" :loading="shipLoading" title="分包裹发快递" width="1300">
      <div>
        <Form ref="groupOrderDeliveryForm" :model="groupOrderDeliveryForm" :label-width="90" :rules="groupOrderDeliverFormValidate" style="position: relative">
          <FormItem label="物流公司" prop="logisticsId">
@@ -571,7 +674,7 @@
          </div>
        </template>
        <template slot="numSlot" slot-scope="{ row, index }">
          <InputNumber :min="0" :max="row.___num - row.deliverNumber" v-model="data[index].canNum">
          <InputNumber :min="0" :max="row.___num - row.deliverNumber - row.returnGoodsNumber" v-model="data[index].canNum">
          </InputNumber>
        </template>
      </Table>
@@ -582,6 +685,16 @@
    </Modal>
    <multipleMap ref="map" @callback="getAddress"></multipleMap>
    <!-- 添加模板编辑弹窗 -->
    <EditTemplateModal
      v-model="editTemplateModalVisible"
      :template-id="currentTemplateId"
      :order-sn="sn"
      :user-check-templates="orderInfo.userCheckTemplates"
      @success="handleTemplateEditSuccess"
      @cancel="editTemplateModalVisible = false"
    />
  </div>
</template>
@@ -589,17 +702,23 @@
import * as API_Order from "@/api/order";
import * as API_Logistics from "@/api/logistics";
import * as RegExp from "@/libs/RegExp.js";
import { getSts } from "@/api/file";
import multipleMap from "@/views/my-components/map/multiple-map";
import EditTemplateModal from "./editTemplateModal.vue";
export default {
  name: "orderDetail",
  components: {
    multipleMap,
    EditTemplateModal
  },
  data () {
    return {
      // isPreviewVisible: false,
      // currentPreviewImage: '',
      // currentPreviewIndex: 0,
      loading:false,
      typeList: [],
      showPrices: false,
@@ -611,6 +730,7 @@
      },
      submitLoading: false, // 添加或编辑提交状态
      logisticsType: 'KUAIDINIAO', //物流类型
      endpoint: '', // 添加endpoint变量用于存储STS endpoint
      someJSONdata: '',
      faceSheetForm: {
@@ -760,7 +880,6 @@
            );
          },
        },
        {
          title: "数量",
          key: "num",
@@ -776,6 +895,55 @@
              this.$options.filters.unitPrice(params.row.flowPrice, "¥")
            );
          },
        },
        {
          title: "退款数量",
          key: "returnGoodsNumber",
          minWidth: 80,
        },
        {
          title: "退款金额",
          key: "refundPrice",
          minWidth: 80,
        },
        {
          title: "退款状态",
          key: "isRefund",
          minWidth: 80,
          render:(h, params) => {
            if(params.row.isRefund==='NO_REFUND'){
              return h(
                "div",
                "未退款"
              );
            }else if(params.row.isRefund==='ALL_REFUND'){
              return h(
                "div",
                { style: {color:"red"} },
                "全部退款"
              );
            }else if(params.row.isRefund==='PART_REFUND'){
              return h(
                "div",
                { style: {color:"red"} },
                "部分退款"
              );
            }else if(params.row.isRefund==='REFUNDING'){
              return h(
                "div",
                { style: {color:"red"} },
                "退款中"
              );
            }
            else {
              return h(
                "div",
                { style: {color:"red"} },
                "未知状态"
              );
            }
          }
        },
      ],
      data: [], // 商品表单数据
@@ -807,6 +975,7 @@
      // 分包裹发货
      groupShipModal: false,
      groupShipModalOpened: false, // 标识分包裹发货弹窗是否已打开过
      shipLoading: true,
      groupOrderDeliveryForm: {
        logisticsNo: "", //发货单号
@@ -849,19 +1018,136 @@
            return h("div", this.$options.filters.unitPrice(params.row.subTotal, "¥"));
          },
        },
        {
          title: "退款数量",
          key: "returnGoodsNumber",
          minWidth: 80,
        },
        {
          title: "退款金额",
          key: "refundPrice",
          minWidth: 80,
        },
        {
          title: "退款状态",
          key: "isRefund",
          minWidth: 80,
          render:(h, params) => {
            if(params.row.isRefund==='NO_REFUND'){
              return h(
                "div",
                "未退款"
              );
            }else if(params.row.isRefund==='ALL_REFUND'){
              return h(
                "div",
                { style: {color:"red"} },
                "全部退款"
              );
            }else if(params.row.isRefund==='PART_REFUND'){
              return h(
                "div",
                { style: {color:"red"} },
                "部分退款"
              );
            }else if(params.row.isRefund==='REFUNDING'){
              return h(
                "div",
                { style: {color:"red"} },
                "退款中"
              );
            }
            else {
              return h(
                "div",
                { style: {color:"red"} },
                "未知状态"
              );
            }
          }
        },
      ],
      orderPackage: [],
      packageTraceList: []
      packageTraceList: [],
      // 添加模板编辑弹窗相关数据
      editTemplateModalVisible: false,
      currentTemplateId: "",
      // 用于传递userCheckTemplates数据给子组件
      userCheckTemplates: [],
      // 添加修改物流单号相关数据
      modifyLogisticsModal: false, // 弹出修改物流单号框
      modifyLogisticsForm: {
        packageNo: "",
        orderSn: "",
        logisticsId: "",
        logisticsNo: "",
        logisticsCode: ""
      },
      modifyLogisticsValidate: {
        logisticsId: [
          { required: true, message: "请选择快递公司", trigger: "change" }
        ],
        logisticsNo: [
          { required: true, message: "请输入快递单号", trigger: "blur" }
        ]
      },
    };
  },
  methods: {
    isUrl(str) {
      if (!str) return false; // 空值直接返回false
      // 正则说明:
      // 1. https?:// :支持http/https协议
      // 2. ([\w-]+\.)+ :允许子域名包含短横线(如lmk-1356772813)
      // 3. [a-zA-Z]{2,} :顶级域名(如com、cn,至少2个字母)
      // 4. (\w-./?%&=)* :URL路径/参数部分,支持常见字符
      // 5. \.(jpg|jpeg|png|gif|bmp|webp)$ :仅匹配常见图片后缀,忽略大小写(i标志)
      const imgReg = /^https?:\/\/([\w-]+\.)+[a-zA-Z]{2,}(\/[\w-./?%&=]*)*\.(jpg|jpeg|png|gif|bmp|webp)$/i;
      return imgReg.test(str);
    },
    // 获取图片URL(使用STS获取的endpoint)
    getImageUrl(fileKey) {
      // 确保fileKey是字符串类型
      if (!fileKey || typeof fileKey !== 'string') {
        console.warn('fileKey is not a valid string:', fileKey);
        return ''; // 返回空字符串或默认图片
      }
      // 安全检查startsWith方法是否存在
      if (fileKey.startsWith && typeof fileKey.startsWith === 'function' &&
          (fileKey.startsWith("http://") || fileKey.startsWith("https://"))) {
        return fileKey;
      }
      // 如果有endpoint配置,使用endpoint拼接URL
      if (this.endpoint) {
        // 确保fileKey不以/开头,endpoint不以/结尾
        const cleanEndpoint = this.endpoint.replace(/\/$/, '');
        const cleanFileKey = fileKey.replace(/^\//, '');
        return `${cleanEndpoint}/${cleanFileKey}`;
      }
      // 否则返回fileKey,让组件自己处理
      return fileKey;
    },
    // 选中
    selectGroupShipGoodsMethods (selected) {
      console.log('selectGroupShipGoodsMethods被调用, selected:', JSON.stringify(selected));
      // 简化逻辑,直接保存选中的商品,数量在提交时从data数组获取
      this.selectGroupShipGoods = selected;
      console.log('selectGroupShipGoods已更新');
    },
    // 分包裹发货
    groupShip () {
      this.groupShipModal = true;
      this.groupShipModalOpened = true; // 标记弹窗已打开
      this.groupOrderDeliveryForm = {
        logisticsNo: "", //发货单号
        logisticsId: "", //物流公司
      }
      this.getLogisticsList();
    },
    // 分页获取物流公司
@@ -874,6 +1160,7 @@
    },
    // 分包裹发货
    confirmShipGroupGoods () {
      console.log('表单原始数据--------------------------》',JSON.stringify(this.selectGroupShipGoods))
      this.$refs.groupOrderDeliveryForm.validate(async (valid) => {
        if (valid) {
          if (this.selectGroupShipGoods.length) {
@@ -881,16 +1168,33 @@
              ...this.groupOrderDeliveryForm,
              orderSn: this.sn,
              partDeliveryDTOList: this.selectGroupShipGoods.map((item) => {
                // 直接从data数组中获取最新的canNum值,而不依赖选择时的数据
                const currentDataItem = this.data.find(d => d.id === item.id);
                const finalDeliveryNum = currentDataItem ? currentDataItem.canNum : item.num;
                console.log('处理商品项:', {
                  id: item.id,
                  selectedCanNum: item.canNum,
                  dataCanNum: currentDataItem?.canNum,
                  num: item.num,
                  finalDeliveryNum: finalDeliveryNum
                });
                return {
                  orderItemId: item.id,
                  deliveryNum: item.canNum ? item.canNum : item.num,
                  deliveryNum: finalDeliveryNum,
                };
              }),
            };
            console.log('---------------------->',JSON.stringify(submit));
            const res = await API_Order.partDelivery(this.sn, submit);
            if (res.success) {
              this.$Message.success("发货成功!");
              this.shipLoading = false;
              // 清空选中的商品数据,避免数据残留
              this.selectGroupShipGoods = [];
              // 重置弹窗打开标识,下次打开时显示默认值
              this.groupShipModalOpened = false;
              this.getDataDetail();
              this.getOrderPackage();
              this.groupShipModal = false;
@@ -987,13 +1291,21 @@
          this.allowOperation = res.result.allowOperationVO;
          if (res.result.orderItems.length) {
            this.data = res.result.orderItems.map((item) => {
              // 只在弹窗打开状态下才保留用户修改的canNum值
              const existingItem = this.groupShipModalOpened ? this.data.find(d => d.id === item.id) : null;
              const defaultCanNum = item.num - item.deliverNumber - item.returnGoodsNumber;
              return {
                ...item,
                ___num: item.num,
                _disabled: item.deliverNumber >= item.num,
                canNum: item.num - item.deliverNumber
                // 如果弹窗已打开且用户已经修改过canNum且值合理,则保留;否则使用默认值
                canNum: (existingItem && existingItem.canNum !== undefined && existingItem.canNum <= defaultCanNum)
                  ? existingItem.canNum
                  : defaultCanNum
              };
            });
            console.log('---------------------->订单详情',this.data)
          }
          this.orderLogData = res.result.orderLogs;
          this.typeList = JSON.parse(JSON.stringify(res.result.order.priceDetailDTO.discountPriceDetail));
@@ -1071,6 +1383,7 @@
      }
    },
    logisticsList () {
      this.packageTraceList = []
      this.logisticsModal = true;
      API_Order.getPackage(this.sn).then((res) => {
        if (res.success && res.result != null) {
@@ -1215,12 +1528,128 @@
      })
    },
    // 获取STS信息
    // async getStsInfo() {
    //   try {
    //     const stsRes = await getSts();
    //     if (stsRes.success) {
    //       this.endpoint = stsRes.data.endpoint;
    //     }
    //   } catch (error) {
    //     console.error('获取STS信息失败:', error);
    //   }
    // },
    // 编辑模板信息
    editTemplateInfo(templateId, orderSn) {
      console.log("调用editTemplateInfo,参数:", {
        templateId: templateId,
        orderSn: orderSn
      });
      this.currentTemplateId = templateId;
      // 更新userCheckTemplates数据
      this.userCheckTemplates = this.orderInfo.userCheckTemplates || [];
      this.editTemplateModalVisible = true;
    },
    // 模板编辑成功回调
    handleTemplateEditSuccess() {
      this.editTemplateModalVisible = false;
      // 可以在这里刷新数据或提示用户
      this.$Message.success("模板信息已更新");
      // 刷新订单详情页面数据
      this.getDataDetail();
    },
    getStsInfo() {
      getSts().then(res => {
        if (res.code==200) {
          this.endpoint = res.data.endpoint;
        }
      });
    },
    // 添加修改物流单号方法
    modifyLogisticsNo() {
      // 获取物流公司列表
      this.getLogisticsList();
      // 初始化表单数据
      this.modifyLogisticsForm.orderSn = this.sn;
      // 如果有包裹信息,初始化第一个包裹的信息
      if (this.orderPackage && this.orderPackage.length > 0) {
        const firstPackage = this.orderPackage[0];
        this.modifyLogisticsForm.packageNo = firstPackage.packageNo || "";
        this.modifyLogisticsForm.logisticsId = firstPackage.logisticsId || "";
        this.modifyLogisticsForm.logisticsNo = firstPackage.logisticsNo || "";
        this.modifyLogisticsForm.logisticsCode = firstPackage.logisticsCode || "";
      }
      this.modifyLogisticsModal = true;
    },
    // 修改指定包裹的物流单号
    modifyPackageLogistics(packageItem) {
      // 获取物流公司列表
      this.getLogisticsList();
      // 初始化表单数据
      this.modifyLogisticsForm.packageNo = packageItem.packageNo || "";
      this.modifyLogisticsForm.orderSn = this.sn;
      this.modifyLogisticsForm.logisticsId = packageItem.logisticsId || "";
      this.modifyLogisticsForm.logisticsNo = packageItem.logisticsNo || "";
      this.modifyLogisticsForm.logisticsCode = packageItem.logisticsCode || "";
      // 关闭查看物流弹窗,打开修改物流弹窗
      this.logisticsModal = false;
      this.modifyLogisticsModal = true;
    },
    // 修改单个物流单号(非分包裹情况)
    modifySingleLogistics() {
      // 获取物流公司列表
      this.getLogisticsList();
      // 初始化表单数据
      this.modifyLogisticsForm.orderSn = this.sn;
      this.modifyLogisticsForm.logisticsId = "";
      this.modifyLogisticsForm.logisticsNo = this.logisticsInfo.logisticCode || "";
      this.modifyLogisticsForm.logisticsCode = "";
      // 关闭查看物流弹窗,打开修改物流弹窗
      this.logisticsModal = false;
      this.modifyLogisticsModal = true;
    },
    // 修改物流单号提交
    modifyLogisticsSubmit() {
      this.$refs.modifyLogisticsForm.validate((valid) => {
        if (valid) {
          API_Order.updateTraces(this.modifyLogisticsForm).then((res) => {
            if (res.success) {
              this.$Message.success("修改物流单号成功");
              this.modifyLogisticsModal = false;
              // 重新获取订单详情和包裹信息
              this.getDataDetail();
              this.getOrderPackage();
              // 重新加载物流信息
              this.checkLogistics();
            } else {
              this.$Message.error(res.message || "修改物流单号失败");
            }
          });
        }
      });
    },
  },
  mounted () {
    this.sn = this.$route.query.sn;
    this.getDataDetail();
    this.getLogisticsSetting();
    this.getOrderPackage();
    this.getStsInfo(); // 添加这行来获取STS信息
  },
  // 如果是从详情页返回列表页,修改列表页keepAlive为true,确保不刷新页面
  beforeRouteLeave (to, from, next) {
@@ -1447,4 +1876,30 @@
    height: inherit;
  }
}
.check-template-list {
  display: flex;
  flex-wrap: wrap; // 关键:多余子项自动换行
  gap: 15px; // 子项之间的间距(水平+垂直)
  padding: 10px 0; // 上下内边距,避免贴边
  width: 100%; // 占满父容器(Col span="12")
}
// 2. 每个模板项:控制宽度和卡片样式
.template-item {
  min-width: 280px; // 最小宽度,避免过窄
  max-width: 350px; // 最大宽度,防止过宽
  flex: 1; // 同一行子项均匀分配宽度
  padding: 12px;
  border: 1px solid #eee;
  border-radius: 6px;
  background-color: #fafafa;
  box-sizing: border-box; // 防止padding撑大宽度
}
.content-img,
.selected-img {
  max-width: 100%; // 适应子项宽度
  max-height: 150px; // 限制最大高度
  border-radius: 4px;
  object-fit: cover; // 保持图片比例,不拉伸
}
</style>