解决方案:

添加了 getConfigUrl 函数来统一处理 URL 获取逻辑
在 iOS 设备上首次进入时保存初始 URL
在所有需要使用 URL 的地方都使用 getConfigUrl 函数
保持了 URL 解码处理
这样修改后:
iOS 设备会使用保存的初始 URL
Android 设备会使用当前 URL
解决了 iOS 设备上 wx.config 可能失败的问题
保持了跨页面的一致性
这个修改应该能解决 iOS 设备上的扫码问题。记得在页面销毁时可以考虑清除 localStorage 中的 URL(如果需要的话)。

我直接上个代码,请大家品尝一下,有需要的自己复制相关的部分即可,不用全复制。

<template>
  <div class="admin-verify-container">
    <van-nav-bar
      title="核销管理"
      left-text="退出"
      right-text="刷新"
      @click-left="handleLogout"
      @click-right="refreshData"
    />

    <div class="actions-bar">
      <van-button type="primary" icon="scan" block @click="startScan">
        扫码核销
      </van-button>
    </div>

    <div class="verify-list-title">
      <h3>今日核销记录 ({{ verifyRecords.length }})</h3>
    </div>

    <div class="verify-list">
      <van-empty v-if="verifyRecords.length === 0" description="暂无核销记录" />

      <van-cell-group v-else inset>
        <van-cell
          v-for="record in verifyRecords"
          :key="record.id"
          :title="record.orderNo"
          :label="`${record.amount}元 | ${record.auditDate}`"
        >
          <template #right-icon>
            <van-tag type="success">已核销</van-tag>
          </template>
        </van-cell>
      </van-cell-group>
    </div>

    <!-- 核销确认对话框 -->
    <van-dialog
      v-model:show="showVerifyDialog"
      title="确认核销"
      show-cancel-button
      @confirm="handleVerify"
      @cancel="cancelVerify"
    >
      <p class="dialog-content">
        是否确认核销该订单?
        <br />
        订单号:{{ scannedOrderId }}
      </p>
    </van-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useRouter } from "vue-router";
import {
  showToast,
  showDialog,
  NavBar as VanNavBar,
  Button as VanButton,
  Empty as VanEmpty,
  CellGroup as VanCellGroup,
  Cell as VanCell,
  Tag as VanTag,
  Dialog as VanDialog
} from "vant";
import "vant/es/nav-bar/style";
import "vant/es/button/style";
import "vant/es/empty/style";
import "vant/es/cell/style";
import "vant/es/cell-group/style";
import "vant/es/tag/style";
import "vant/es/dialog/style";
import "vant/es/toast/style";
import adminHttp, {
  getAdminToken,
  removeAdminToken
} from "@/utils/http/adminHttp";

// 删除TypeScript接口声明

interface VerifyRecord {
  id: number;
  orderNo: string;
  amount: number;
  time: string;
  auditDate: string;
}

// 定义API响应接口
interface ApiResponse<T = any> {
  code: number;
  msg: string;
  data?: T;
  rows?: T;
}

const router = useRouter();
const verifyRecords = ref<VerifyRecord[]>([]);
const loading = ref(false);
const showScanResult = ref(false);
const scanResult = ref<VerifyRecord | null>(null);
const verifying = ref(false);
const scannedOrderId = ref("");
const showVerifyDialog = ref(false);

// 日期相关的计算属性
const dateRange = computed(() => {
  const today = new Date();

  // 设置为当天的起始时间 (00:00:00.000)
  const startDate = new Date(today);
  startDate.setHours(0, 0, 0, 0);

  // 设置为当天的结束时间 (23:59:59.999)
  const endDate = new Date(today);
  endDate.setHours(23, 59, 59, 999);

  // 格式化日期为后端所需格式 yyyy-MM-dd HH:mm:ss
  const formatDate = (date: Date): string => {
    const pad = (num: number): string => String(num).padStart(2, "0");

    const year = date.getFullYear();
    const month = pad(date.getMonth() + 1);
    const day = pad(date.getDate());
    const hours = pad(date.getHours());
    const minutes = pad(date.getMinutes());
    const seconds = pad(date.getSeconds());

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  };

  return {
    orderDateStart: formatDate(startDate),
    orderDateEnd: formatDate(endDate)
  };
});

// 检查登录状态
const checkAdminLogin = () => {
  const adminToken = getAdminToken();
  if (!adminToken) {
    showToast("请先登录");
    router.replace("/admin/login");
    return false;
  }
  return true;
};

// 获取核销记录
const fetchVerifyRecords = async () => {
  if (!checkAdminLogin()) return;

  loading.value = true;

  try {
    // 使用计算属性中的日期范围
    const { orderDateStart, orderDateEnd } = dateRange.value;
    const response = await adminHttp.request<ApiResponse<VerifyRecord[]>>({
      url: "/vae/order/list",
      method: "GET",
      params: {
        status: 2,
        type: 1,
        orderDateStart,
        orderDateEnd
      },
      headers: {
        Authorization: `Bearer ${getAdminToken()}`
      }
    });

    if (response.code === 200 && response.rows) {
      verifyRecords.value = response.rows;
    } else {
      showToast({
        message: response.msg || "获取记录失败",
        type: "fail"
      });
    }
  } catch (error) {
    console.error("获取核销记录失败:", error);
    showToast({
      message: "获取记录失败,请稍后再试",
      type: "fail"
    });
  } finally {
    loading.value = false;
  }
};

// 刷新数据
const refreshData = () => {
  fetchVerifyRecords();
};

// 1. 添加错误重试机制
let retryCount = 0;
const maxRetries = 3;

const initWithRetry = async () => {
  try {
    await initWxConfig();
  } catch (error) {
    if (retryCount < maxRetries) {
      retryCount++;
      console.log(`第 ${retryCount} 次重试初始化`);
      setTimeout(initWithRetry, 1000);
    }
  }
};

// 2. 在调用扫码前再次检查配置
const checkWxReady = () => {
  return new Promise((resolve) => {
    if (typeof (window as any).wx !== "undefined") {
      (window as any).wx.ready(() => {
        resolve(true);
      });
    } else {
      resolve(false);
    }
  });
};

// 添加 getConfigUrl 函数
const getConfigUrl = () => {
  let u = window.navigator.userAgent;
  let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
  let url = '';
  
  if (isiOS) {
    // iOS 需要使用进入页面的初始 URL
    url = window.localStorage.getItem('_iosWXConfig_') || window.location.href.split('#')[0];
  } else {
    // 安卓使用当前 URL
    url = window.location.href.split('#')[0];
  }
  return url;
};

// 修改 startScan 方法中获取 URL 的部分
const startScan = async () => {
  if (!checkAdminLogin()) return;

  try {
    // 使用 getConfigUrl 获取正确的 URL
    const currentUrl = decodeURIComponent(getConfigUrl());
    console.log("当前URL:", currentUrl);

    // 如果是 iOS 设备,保存初始 URL
    if (!!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
      window.localStorage.setItem('_iosWXConfig_', currentUrl);
    }

    // 获取微信配置参数
    const response = await adminHttp.request<ApiResponse>({
      url: "/app/auth/jsSign",
      method: "POST",
      data: {
        url: currentUrl
      }
    });

    if (response.code !== 200 || !response.data) {
      showToast("获取配置失败");
      return;
    }

    console.log("获取到的微信配置:", response.data);

    // 配置微信JSSDK
    (window as any).wx.config({
      debug: false,
      appId: response.data.appId,
      timestamp: response.data.timestamp,
      nonceStr: response.data.nonceStr,
      signature: response.data.signature,
      jsApiList: ["scanQRCode"]
    });

    // 等待配置就绪后再调用扫码
    (window as any).wx.ready(() => {
      console.log("微信JSSDK配置就绪,开始调用扫码");
      callWxScan();
    });

    // 配置错误处理
    (window as any).wx.error((res: any) => {
      console.error("微信JSSDK配置失败", {
        error: res,
        url: currentUrl,
        config: response.data
      });
      showToast("初始化失败,请重试");
    });

  } catch (error) {
    console.error("初始化微信配置错误", error);
    showToast("配置失败,请重试");
  }
};

// 将实际的扫码调用抽离为独立方法
const callWxScan = () => {
  if (typeof (window as any).wx === "undefined") {
    showToast("微信环境未初始化");
    return;
  }

  (window as any).wx.scanQRCode({
    needResult: 1,
    scanType: ["qrCode"],
    success: function (res: any) {
      console.log("扫码成功", res);
      handleScanResult(res.resultStr);
    },
    fail: function (error: any) {
      console.error("扫码失败详情:", {
        error,
        stack: error?.stack,
        message: error?.message
      });
      showToast("扫码失败,请重试");
    },
    cancel: function () {
      console.log("用户取消扫码");
      showToast("已取消扫码");
    },
    complete: function (res: any) {
      console.log("扫码完成状态:", res);
    }
  });
};

// 处理扫码结果
const handleScanResult = async (result: string) => {
  if (!result) {
    showToast("无效的二维码");
    return;
  }

  // 保存扫描到的订单ID
  scannedOrderId.value = result;
  // 添加调试信息
  showToast(`扫描到订单号: ${result}`);

  // 显示确认对话框
  showVerifyDialog.value = true;
};

// 核销方法
const handleVerify = async () => {
  // 添加调试信息
  // showToast(`正在核销订单: ${scannedOrderId.value}`);

  const response = await adminHttp.request<ApiResponse>({
    url: `/vae/order/audit/${scannedOrderId.value}`,
    method: "GET"
  });

  if (response.code === 200) {
    refreshData();
    showToast({
      message: "核销成功",
      type: "success"
    });
    // 重置状态
    showVerifyDialog.value = false;
    scannedOrderId.value = "";
    showScanResult.value = false;

    // 刷新页面
    location.reload();
  } else {
    showToast({
      message: response.msg || "核销失败",
      type: "fail"
    });
  }
};

// 取消核销
const cancelVerify = () => {
  showVerifyDialog.value = false;
  scannedOrderId.value = "";
};

// 退出登录
const handleLogout = () => {
  showDialog({
    title: "确认退出",
    message: "是否确认退出管理系统?",
    showCancelButton: true,
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    confirmButtonColor: "#ff6b00"
  })
    .then(() => {
      // 清除管理员token
      removeAdminToken();
      // 跳转到登录页
      router.replace("/admin/login");
    })
    .catch(() => {
      // 用户取消,不做操作
    });
};

// 修改 initWxConfig 方法中获取 URL 的部分
const initWxConfig = async () => {
  if (typeof (window as any).wx === "undefined") {
    console.warn("未检测到微信环境");
    return;
  }

  try {
    // 使用 getConfigUrl 获取正确的 URL
    const currentUrl = decodeURIComponent(getConfigUrl());
    console.log("当前URL:", currentUrl);

    // 如果是 iOS 设备,保存初始 URL
    if (!!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
      window.localStorage.setItem('_iosWXConfig_', currentUrl);
    }

    // 获取微信配置参数
    const response = await adminHttp.request<ApiResponse>({
      url: "/app/auth/jsSign",
      method: "POST",
      data: {
        url: currentUrl
      }
    });
    console.log("签名请求结果:", response);

    if (response.code === 200 && response.data) {
      console.log("获取到的微信配置:", response.data);

      // 配置微信JSSDK
      (window as any).wx.config({
        debug: false, // 临时开启调试模式,排查问题后记得关闭
        appId: response.data.appId,
        timestamp: response.data.timestamp,
        nonceStr: response.data.nonceStr,
        signature: response.data.signature,
        jsApiList: ["scanQRCode"],
        openTagList: ["wx-open-launch-weapp"] // 如果需要的话添加
      });

      (window as any).wx.ready(function () {
        console.log("微信JSSDK配置就绪");
        // 可以在这里预检查一下 API
        (window as any).wx.checkJsApi({
          jsApiList: ["scanQRCode"],
          success: function(res: any) {
            console.log("初始化时API检查结果:", res);
          }
        });
      });

      (window as any).wx.error(function (res: any) {
        console.error("微信JSSDK配置失败", {
          error: res,
          url: currentUrl,
          config: response.data
        });
      });
    }
  } catch (error) {
    console.error("初始化微信配置错误", error);
  }
};

// 添加一个重新初始化的方法
const reinitWxConfig = () => {
  console.log("尝试重新初始化微信配置");
  initWxConfig();
};

onMounted(() => {
  if (checkAdminLogin()) {
    fetchVerifyRecords();
  }
});
</script>

<style scoped>
.admin-verify-container {
  min-height: 100vh;
  background: #f5f7fa;
}

.actions-bar {
  padding: 16px;
}

.verify-list-title {
  padding: 0 16px;
  margin-bottom: 10px;
}

.verify-list-title h3 {
  font-size: 16px;
  color: #333;
  margin: 0;
}

.verify-list {
  padding-bottom: 20px;
}

.scan-result {
  padding: 20px;
}

.scan-item {
  margin-bottom: 15px;
  display: flex;
  justify-content: space-between;
}

.scan-item .label {
  color: #999;
}

.scan-item .value {
  color: #333;
  font-weight: 500;
}

.scan-item .highlight {
  color: #ff6b00;
  font-size: 18px;
  font-weight: bold;
}

.scan-actions {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

:deep(.van-nav-bar__text) {
  color: #000;
}

:deep(.van-button--primary) {
  background-color: #ff6b00;
  border-color: #ff6b00;
}

:deep(.van-dialog__confirm) {
  color: #ff6b00;
}

:deep(.van-dialog__header) {
  padding-top: 20px;
}

.dialog-content {
  padding: 20px;
  text-align: center;
  line-height: 1.5;
}
</style>




Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐