Browse Source

fix: 恢复补货员绑定码二维码生成,新增小程序端扫码识别

- Web 后台绑定码弹窗恢复二维码生成(qrcode 编码绑定码纯文本)
- 运营端小程序登录页绑定码输入区新增「扫码」按钮,调用 uni.scanCode
  扫描二维码自动识别 24 位绑定码并填入输入框
- 补货员操作步骤指引同步更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 1 tuần trước cách đây
mục cha
commit
d1ae14bb2a

+ 56 - 7
haha-admin-mp/src/pages/login/login.vue

@@ -64,16 +64,19 @@
         <!-- 绑定码登录 -->
         <view class="bind-section" v-if="showBindInput">
           <view class="divider-line"></view>
-          <text class="bind-hint">该微信未绑定补货员账号,请输入管理员提供的一次性绑定码:</text>
+          <text class="bind-hint">该微信未绑定补货员账号,请扫码或输入管理员提供的一次性绑定码:</text>
           <view class="bind-input-wrapper">
-            <input 
-              class="bind-input" 
-              type="text" 
-              v-model="bindingCode" 
+            <input
+              class="bind-input"
+              type="text"
+              v-model="bindingCode"
               placeholder="请输入24位绑定码"
               placeholder-class="input-placeholder"
               maxlength="24"
             />
+            <view class="scan-bind-btn" @click="handleScanBindCode">
+              <text class="scan-icon-text">扫码</text>
+            </view>
           </view>
           <button class="bind-btn" :loading="bindLoading" :disabled="bindLoading || !bindingCode" @click="handleBindWechat">
             <text v-if="!bindLoading">绑定微信并登录</text>
@@ -182,6 +185,29 @@ const handleWechatLogin = async () => {
   }
 };
 
+/**
+ * 扫码识别绑定码
+ */
+const handleScanBindCode = () => {
+  uni.scanCode({
+    onlyFromCamera: false,
+    scanType: ['qrCode'],
+    success: (res: any) => {
+      const result = (res.result || '').trim();
+      // 绑定码为24位大写字母数字组合
+      if (/^[A-Z0-9]{24}$/.test(result)) {
+        bindingCode.value = result;
+        uni.showToast({ title: '已识别绑定码', icon: 'success' });
+      } else {
+        uni.showToast({ title: '无效的绑定码二维码', icon: 'none' });
+      }
+    },
+    fail: () => {
+      // 用户取消扫码
+    }
+  });
+};
+
 /**
  * 补货员绑定微信
  */
@@ -648,6 +674,8 @@ const handleBindWechat = async () => {
   }
   
   .bind-input-wrapper {
+    display: flex;
+    align-items: center;
     background: $bg-color-page;
     border: 2rpx solid $border-color;
     border-radius: 16rpx;
@@ -662,14 +690,35 @@ const handleBindWechat = async () => {
     }
     
     .bind-input {
-      width: 100%;
+      flex: 1;
       height: 100rpx;
       font-size: 30rpx;
       color: $text-color-primary;
       letter-spacing: 4rpx;
     }
+
+    .scan-bind-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 72rpx;
+      padding: 0 24rpx;
+      margin-right: 8rpx;
+      background: $primary-color;
+      border-radius: 12rpx;
+
+      .scan-icon-text {
+        font-size: 24rpx;
+        color: #fff;
+        font-weight: 600;
+      }
+
+      &:active {
+        opacity: 0.85;
+      }
+    }
   }
-  
+
   .bind-btn {
     width: 100%;
     height: 90rpx;

+ 39 - 2
haha-admin-web/src/views/replenisher/index.vue

@@ -303,14 +303,23 @@ const {
             <div class="binding-code-hint">点击绑定码可复制</div>
           </div>
 
+          <!-- 二维码展示 -->
+          <div v-if="bindingCodeData.qrCodeDataUrl" class="qr-section">
+            <div class="section-label">绑定二维码</div>
+            <div class="qr-wrapper">
+              <img :src="bindingCodeData.qrCodeDataUrl" alt="绑定二维码" class="qr-image" />
+            </div>
+            <div class="qr-hint">补货员可在小程序登录页扫码识别</div>
+          </div>
+
           <!-- 操作指引 -->
           <div class="manual-section">
             <div class="section-label">补货员操作步骤</div>
             <div class="manual-content">
               <div class="manual-step">1. 打开"哈哈运营平台"小程序</div>
               <div class="manual-step">2. 在登录页点击"已有绑定码?点击绑定微信"</div>
-              <div class="manual-step">3. 输入上方 24 位绑定码,授权微信完成绑定</div>
-              <div class="manual-step">4. 绑定成功后自动进入补货首页,即可开始补货</div>
+              <div class="manual-step">3. 点击"扫码识别"扫描上方二维码,或手动输入绑定码</div>
+              <div class="manual-step">4. 授权微信完成绑定,自动进入补货首页</div>
             </div>
           </div>
         </template>
@@ -477,6 +486,34 @@ const {
     }
   }
 
+  .qr-section {
+    margin-bottom: 24px;
+
+    .qr-wrapper {
+      display: flex;
+      justify-content: center;
+      background: #fff;
+      border: 1px solid var(--el-border-color-lighter);
+      border-radius: 8px;
+      padding: 16px;
+      width: fit-content;
+      margin: 0 auto;
+
+      .qr-image {
+        width: 200px;
+        height: 200px;
+        display: block;
+      }
+    }
+
+    .qr-hint {
+      font-size: 12px;
+      color: var(--el-text-color-placeholder);
+      margin-top: 6px;
+      text-align: center;
+    }
+  }
+
   .manual-section {
     margin-bottom: 8px;
 

+ 20 - 1
haha-admin-web/src/views/replenisher/utils/hook.tsx

@@ -2,6 +2,7 @@ import dayjs from "dayjs";
 import { message } from "@/utils/message";
 import { addDialog } from "@/components/ReDialog";
 import { usePublicHooks } from "@/views/system/hooks";
+import QRCode from "qrcode";
 import { deviceDetection } from "@pureadmin/utils";
 import type { PaginationProps } from "@pureadmin/table";
 import { getReplenisherList,
@@ -330,15 +331,18 @@ export function useReplenisher(tableRef: Ref) {
   const bindingCodeData = reactive<{
     replenisherName: string;
     bindingCode: string;
+    qrCodeDataUrl: string;
   }>({
     replenisherName: "",
-    bindingCode: ""
+    bindingCode: "",
+    qrCodeDataUrl: ""
   });
   const generatingCode = ref(false);
 
   function openBindingCodeDialog(row: ReplenisherFormItem) {
     bindingCodeData.replenisherName = row.name;
     bindingCodeData.bindingCode = "";
+    bindingCodeData.qrCodeDataUrl = "";
     bindingDialogVisible.value = true;
     handleGenerateAndShowBindingCode(row);
   }
@@ -361,6 +365,21 @@ export function useReplenisher(tableRef: Ref) {
         const code = res.data.bindingCode;
         bindingCodeData.bindingCode = code;
 
+        // 生成二维码(将绑定码编码为二维码,补货员可在小程序登录页扫码识别)
+        try {
+          bindingCodeData.qrCodeDataUrl = await QRCode.toDataURL(code, {
+            width: 280,
+            margin: 2,
+            color: {
+              dark: "#1e293b",
+              light: "#ffffff"
+            }
+          });
+        } catch (qrErr) {
+          console.error("生成二维码失败:", qrErr);
+          bindingCodeData.qrCodeDataUrl = "";
+        }
+
         // 刷新列表更新 wechatOpenid 状态
         onSearch();
       } else {