Sfoglia il codice sorgente

feat: h5一键登录

lvkun996 2 settimane fa
parent
commit
8caf53c339

+ 11 - 0
bug总结.md

@@ -0,0 +1,11 @@
+
+
+# 设备
+    1. 长按开机后,有时候会接着关机
+    2. 开机播放关机动画,直接关机
+
+# 蓝牙
+    1. 蓝牙连接成功,wifi错误,然后绑定失败,就不能再次连接了?
+    2. 强制出现二维码(ai键 + 菜单键)
+    3. 鸿蒙6系统连接蓝牙有问题
+

+ 35 - 1
package-lock.json

@@ -25,8 +25,10 @@
         "@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
         "crypto-js": "^4.2.0",
         "sass-loader": "10",
+        "vconsole": "^3.15.1",
         "vue": "^3.4.21",
-        "vue-i18n": "^9.1.9"
+        "vue-i18n": "^9.1.9",
+        "weixin-js-sdk": "^1.6.5"
       },
       "devDependencies": {
         "@dcloudio/types": "^3.4.8",
@@ -5523,6 +5525,17 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/copy-text-to-clipboard": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz",
+      "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/core-js": {
       "version": "3.47.0",
       "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.47.0.tgz",
@@ -8488,6 +8501,11 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/mutation-observer": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
+      "integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
+    },
     "node_modules/nanoid": {
       "version": "3.3.11",
       "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
@@ -10921,6 +10939,17 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/vconsole": {
+      "version": "3.15.1",
+      "resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
+      "integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
+      "dependencies": {
+        "@babel/runtime": "^7.17.2",
+        "copy-text-to-clipboard": "^3.0.1",
+        "core-js": "^3.11.0",
+        "mutation-observer": "^1.0.3"
+      }
+    },
     "node_modules/vite": {
       "version": "5.2.8",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.2.8.tgz",
@@ -11366,6 +11395,11 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/weixin-js-sdk": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.5.tgz",
+      "integrity": "sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ=="
+    },
     "node_modules/whatwg-encoding": {
       "version": "1.0.5",
       "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",

+ 8 - 5
package.json

@@ -2,8 +2,9 @@
   "name": "uni-preset-vue",
   "version": "0.0.0",
   "scripts": {
+    "upload:h5": "python upload.py /usr/share/nginx/html/luojigou/ g",
     "dev:custom": "uni -p",
-    "dev:h5": "uni",
+    "dev:h5": "node scripts/switch-pages.js h5 && uni",
     "dev:h5:ssr": "uni --ssr",
     "dev:mp-alipay": "uni -p mp-alipay",
     "dev:mp-baidu": "uni -p mp-baidu",
@@ -12,13 +13,13 @@
     "dev:mp-lark": "uni -p mp-lark",
     "dev:mp-qq": "uni -p mp-qq",
     "dev:mp-toutiao": "uni -p mp-toutiao",
-    "dev:mp-weixin": "uni -p mp-weixin",
+    "dev:mp-weixin": "node scripts/switch-pages.js mp-weixin && uni -p mp-weixin",
     "dev:mp-xhs": "uni -p mp-xhs",
     "dev:quickapp-webview": "uni -p quickapp-webview",
     "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
     "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
     "build:custom": "uni build -p",
-    "build:h5": "uni build",
+    "build:h5": "node scripts/switch-pages.js h5 && uni build",
     "build:h5:ssr": "uni build --ssr",
     "build:mp-alipay": "uni build -p mp-alipay",
     "build:mp-baidu": "uni build -p mp-baidu",
@@ -27,7 +28,7 @@
     "build:mp-lark": "uni build -p mp-lark",
     "build:mp-qq": "uni build -p mp-qq",
     "build:mp-toutiao": "uni build -p mp-toutiao",
-    "build:mp-weixin": "uni build -p mp-weixin",
+    "build:mp-weixin": "node scripts/switch-pages.js mp-weixin && uni build -p mp-weixin",
     "build:mp-xhs": "uni build -p mp-xhs",
     "build:quickapp-webview": "uni build -p quickapp-webview",
     "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
@@ -52,8 +53,10 @@
     "@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
     "crypto-js": "^4.2.0",
     "sass-loader": "10",
+    "vconsole": "^3.15.1",
     "vue": "^3.4.21",
-    "vue-i18n": "^9.1.9"
+    "vue-i18n": "^9.1.9",
+    "weixin-js-sdk": "^1.6.5"
   },
   "devDependencies": {
     "@dcloudio/types": "^3.4.8",

+ 4 - 0
readme.md

@@ -0,0 +1,4 @@
+
+
+# 错误码
+    20002: wifi密码错误

+ 69 - 0
scripts/switch-pages.js

@@ -0,0 +1,69 @@
+/**
+ * 根据 H5 或小程序环境切换 pages.json 配置
+ * 用法: node scripts/switch-pages.js h5 | node scripts/switch-pages.js mp-weixin
+ */
+
+const fs = require('fs')
+const path = require('path')
+
+const pagesJsonPath = path.resolve(__dirname, '../src/pages.json')
+
+// H5 页面配置
+const h5Pages = [
+  {
+    path: 'pages/h5/index'
+  }
+]
+
+// 小程序页面配置
+const mpPages = [
+  {
+    path: 'pages/devices/index',
+    style: {
+      navigationBarTitleText: '我的设备'
+    }
+  },
+  {
+    path: 'pages/index/result',
+    style: {
+      navigationBarTitleText: '配网结果'
+    }
+  },
+  {
+    path: 'pages/index/index',
+    style: {
+      navigationBarTitleText: '设备配网'
+    }
+  },
+  {
+    path: 'pages/login/login',
+    style: {
+      navigationBarTitleText: '登录'
+    }
+  },
+  {
+    path: 'pages/login/register',
+    style: {
+      navigationBarTitleText: '注册'
+    }
+  }
+]
+
+const target = process.argv[2]
+
+if (!target || !['h5', 'mp-weixin'].includes(target)) {
+  console.log('请指定目标平台: h5 或 mp-weixin')
+  process.exit(1)
+}
+
+try {
+  const pagesJson = JSON.parse(fs.readFileSync(pagesJsonPath, 'utf-8'))
+  
+  pagesJson.pages = target === 'h5' ? h5Pages : mpPages
+  
+  fs.writeFileSync(pagesJsonPath, JSON.stringify(pagesJson, null, '\t'), 'utf-8')
+  console.log(`已切换到 ${target} 模式`)
+} catch (err) {
+  console.error('切换失败:', err)
+  process.exit(1)
+}

+ 10 - 0
src/api/h5.ts

@@ -0,0 +1,10 @@
+import { request } from "@/service/request"
+
+
+
+export const getH5Config = () => {
+  return request({
+    url: 'https://open.api.luojigou.vip/xiaodou-ai/mobile/home/register/config',
+    method: 'GET'
+  })
+}

+ 13 - 9
src/api/index.ts

@@ -1,5 +1,6 @@
 import { generatesSignatureStr } from "@/utils/tools"
 import { request } from "@/service/request"
+import APPConfig from "@/config/index"
 
 
 interface RESP<T = any> {
@@ -11,42 +12,45 @@ interface RESP<T = any> {
 
 // 删除设备
 // deviceName: 蓝牙名称
-export const deleteDevice = ( snCode: string, deviceName: string): Promise<RESP> => {
-  const data = JSON.stringify({snCode, deviceName})
+export const deleteDevice = (snCode: string, deviceName: string): Promise<RESP> => {
+  const deviceType = APPConfig.appType
+  const data = JSON.stringify({snCode, deviceName, deviceType})
   const reqQueryParams = generatesSignatureStr(data)
   console.log("签名:", reqQueryParams.signature);
   
   return request({
     url: `https://open.api.luojigou.vip/xiaodou-ai/mobile/home/device?timestamp=${reqQueryParams.timestamp}&signature=${reqQueryParams.signature}`,
     method: "PUT",
-    params: {snCode, deviceName}
+    params: {snCode, deviceName, deviceType}
   })
 }
 
 
 
 export const getDeviceList = (): Promise<RESP<API.DEVICE[]>> => {
+  const deviceType = APPConfig.appType
   const reqQueryParams = generatesSignatureStr()
   console.log("签名:", reqQueryParams.signature);
-  
   return request({
-    url: `https://open.api.luojigou.vip/xiaodou-ai//mobile/home/device?timestamp=${reqQueryParams.timestamp}&signature=${reqQueryParams.signature}`,
+    url: `https://open.api.luojigou.vip/xiaodou-ai/mobile/home/device/${deviceType}?timestamp=${reqQueryParams.timestamp}&signature=${reqQueryParams.signature}`,
     method: "GET",
-    params: {timestamp: reqQueryParams.timestamp}
+    params: {timestamp: reqQueryParams.timestamp, deviceType}
   })
 }
 
 
 // 500是错误
-export const bindDevice = ( snCode: string,  deviceName: string): Promise<RESP> => {
-  const data = JSON.stringify({snCode, deviceName})
+export const bindDevice = (snCode: string, deviceName: string): Promise<RESP> => {
+  const deviceType = APPConfig.appType
+  const _deviceName = deviceName ? deviceName : "故事机"
+  const data = JSON.stringify({snCode, deviceName: _deviceName, deviceType})
   const reqQueryParams = generatesSignatureStr(data)
   console.log("签名:", reqQueryParams.signature);
   
   return request({
     url: `https://open.api.luojigou.vip/xiaodou-ai/mobile/home/device?timestamp=${reqQueryParams.timestamp}&signature=${reqQueryParams.signature}`,
     method: "POST",
-    params: {snCode, deviceName}
+    params: {snCode, deviceName: _deviceName, deviceType}
   })
 }
 

+ 3 - 0
src/main.ts

@@ -2,6 +2,9 @@ import { createSSRApp } from "vue";
 import App from "./App.vue";
 export function createApp() {
   const app = createSSRApp(App);
+
+  // app.config. = ['wx-open-launch-weapp'];
+app.config.isCustomElement = tag => ['wx-open-launch-app'].includes(tag)
   return {
     app,
   };

+ 2 - 29
src/pages.json

@@ -1,34 +1,7 @@
 {
 	"pages": [
 		{
-			"path": "pages/devices/index",
-			"style": {
-				"navigationBarTitleText": "我的设备"
-			}
-		},
-			{
-			"path": "pages/index/result",
-			"style": {
-				"navigationBarTitleText": "配网结果"
-			}
-		},
-		{
-			"path": "pages/index/index",
-			"style": {
-				"navigationBarTitleText": "设备配网"
-			}
-		},
-		{
-			"path": "pages/login/login",
-			"style": {
-				"navigationBarTitleText": "登录"
-			}
-		},
-		{
-			"path": "pages/login/register",
-			"style": {
-				"navigationBarTitleText": "注册"
-			}
+			"path": "pages/h5/index"
 		}
 	],
 	"globalStyle": {
@@ -37,4 +10,4 @@
 		"navigationBarBackgroundColor": "#F8F8F8",
 		"backgroundColor": "#F8F8F8"
 	}
-}
+}

+ 157 - 0
src/pages/h5/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <view class="page-container">
+    <!-- 微信开放标签跳转小程序 -->
+    <wx-open-launch-weapp
+      id="launch-btn"
+      appid="wxfc14dfa7481c7f18"
+      :path="miniProgramPath"
+      style="width: 100%;height: 100px;background-color: #000;"
+    >
+
+      <div  v-is="'script'" type="text/wxtag-template">
+        <div  v-is="'style'" >
+          .btn {
+            width: 280px;
+            height: 44px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: #fff;
+            font-size: 16px;
+            font-weight: 600;
+            border-radius: 22px;
+            border: none;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+        </div>
+         <button class="btn">打开配网小程序</button>
+      </div >
+    </wx-open-launch-weapp>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+// import wx from '@/utils/jsweixin'
+import { getH5Config } from '@/api/h5'
+import VConsole from 'vconsole'
+import wx from "weixin-js-sdk"
+
+// H5 环境启用 vconsole
+if (typeof window !== 'undefined') {
+  new VConsole()
+}
+
+const mac = ref('')
+
+
+const styles = `width: 334px;
+            height: 52px;
+            background: #ff8024;
+            border-radius: 26px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-size: 18px;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: 400;
+            color: #ffffff;`
+
+
+const html1 = '<div style="width:100%; height: 58px; line-height: 58px; border-radius: 29px; text-align: center; background: #FFFFFF;color: #00CE3D; font-size: 22px; font-family: SimHei;font-weight: 400">进入APP查看</div>'
+
+const html = `   <style >
+          .btn {
+            width: 280px;
+            height: 44px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: #fff;
+            font-size: 16px;
+            font-weight: 600;
+            border-radius: 22px;
+            border: none;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+        </style>
+        <button class="btn">打开配网小程序</button>`
+
+const miniProgramPath = computed(() => {
+  return `pages/devices/index`
+})
+
+onMounted(() => {
+  // 从 URL 中提取 MAC 地址
+  // URL 格式: https://luojigou.vip/g/98A34635D252
+  const path = window.location.pathname
+  const parts = path.split('/')
+  mac.value = parts[parts.length - 1].toUpperCase()
+  console.log('提取的 MAC:', mac.value)
+
+  initWxConfig()
+
+
+})
+
+const initWxConfig = async () => {
+  const ua = navigator.userAgent.toLowerCase()
+  const isWechat = ua.indexOf('micromessenger') !== -1
+  
+  if (!isWechat) {
+    console.log('非微信环境')
+    return
+  }
+
+  try {
+    const { data } = await getH5Config()
+    wx.config({
+      debug: true,
+      appId: data.appId,
+      timestamp: data.timestamp,
+      nonceStr: data.nonceStr,
+      signature: data.signature,
+      jsApiList: [],
+      openTagList: ['wx-open-launch-weapp']
+    })
+
+    wx.ready(function(){
+      console.log("ready");
+
+        var btn = document.getElementById('launch-btn');
+            btn.addEventListener('ready', function (e) {
+                console.log(e);
+                
+                console.log('btn ready') // 未开启 debug 时,真机不会打印;开启则正常打印
+              })
+            btn.addEventListener('launch', function (e) {
+                console.log('success');
+            });
+            btn.addEventListener('error', function (e) {
+                console.log('fail', e.detail);
+            });
+    })
+
+    
+    wx.error(function(){
+      console.log("error");
+    })
+  } catch (err) {
+    console.log('获取微信配置失败:', err)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+wx-open-launch-weapp {
+  display: block;
+}
+</style>

+ 14 - 15
src/pages/index/index.vue

@@ -70,6 +70,7 @@ import WifiListComponent from './components/wifi-list.vue'
 import SuccessPage from './components/success-page.vue'
 import APPConfig from '@/config/index'
 import { AppTypeEnum } from '@/enum/index'
+import { getBeaconInfo } from '@/utils/template';
 
 // 定义步骤状态:0=开始, 1=扫描蓝牙, 2=选择WiFi, 3=完成
 const currentStep = ref<number>(0);
@@ -180,7 +181,6 @@ const goDevicesPage = () => {
 const startScan = () => {
   // 故事机 扫描二维码 获取蓝牙信息 进行链接
   // 小逗AI 扫描物理设备蓝牙 进行链接
-  console.log('APPConfig.appType:', APPConfig.appType);
   uni.openBluetoothAdapter({
     success: (res) => {
       if (APPConfig.appType === AppTypeEnum.StoryMachine) {
@@ -190,7 +190,12 @@ const startScan = () => {
           success: (res) => {
             loadingBluetoothConnect.value = true
             console.log("扫码结果:", res);
-            const mac = res.result
+            
+            // 二维码格式: https://luojigou.vip/g/98A34635D252
+            // 提取 MAC 地址(URL 最后一段)
+            const urlParts = res.result.split('/')
+            const mac = urlParts[urlParts.length - 1].toUpperCase()
+            console.log("提取的MAC:", mac);
 
             const isIOS = uni.getSystemInfoSync().platform === 'ios'
 
@@ -200,32 +205,26 @@ const startScan = () => {
               return
             }
 
-            // iOS 不支持 MAC 直连,需要先扫描,从广播数据中匹配目标设备
-            const targetMac = mac.toUpperCase().replace(/:/g, '')
-     
+            // iOS 不支持 MAC 直连,需要扫描并通过设备名称匹配
+            // 设备名称格式: XDS_ + MAC (如 XDS_98A34635D252)
+            const targetDeviceName = `XDS_${mac}`
             currentStep.value = 1
 
             uni.startBluetoothDevicesDiscovery({
               allowDuplicatesKey: false,
               success: () => {
-                console.log('iOS: 开始扫描,目标MAC:', targetMac)
+                console.log('iOS: 开始扫描,目标设备名:', targetDeviceName)
                 const scanTimeout = setTimeout(() => {
                   uni.stopBluetoothDevicesDiscovery({})
                   uni.offBluetoothDeviceFound()
+                  loadingBluetoothConnect.value = false
                   uni.showToast({ title: '未找到设备,请靠近后重试', icon: 'none' })
                 }, 15000)
 
                 uni.onBluetoothDeviceFound((foundRes) => {
                   for (const device of foundRes.devices) {
-                    // iOS deviceId 是 UUID,从广播数据里匹配 MAC
-                    let macInAdvert = ''
-                    if (device.advertisData) {
-                   
-                      const bytes = new Uint8Array(device.advertisData)
-                      macInAdvert = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase()
-                      console.log('当前蓝牙设备的广播数据段中的 ManufacturerData 数据段备:', macInAdvert)
-                    }
-                    if (macInAdvert.includes(targetMac)) {
+                    console.log('扫描到设备:', device.name, device.deviceId)
+                    if (device.name === targetDeviceName) {
                       clearTimeout(scanTimeout)
                       uni.stopBluetoothDevicesDiscovery({})
                       uni.offBluetoothDeviceFound()

+ 7 - 0
src/utils/bridge.ts

@@ -119,6 +119,13 @@ export const buildWIFICmdpacket = () => {
   })
 }
 
+// 此时是收到了wifi列表
+  //  buildBlePacket({
+  //   cmd: 2,  、
+  //   value: {"code": 10000},
+  //   msgid: "ab212"
+  // })
+
 /**
  * 
  *  @description 发送 WIFI 扫描请求

File diff suppressed because it is too large
+ 0 - 0
src/utils/jsweixin.js


+ 39 - 0
src/utils/jsweixin.ts

@@ -0,0 +1,39 @@
+/**
+ * 微信 JS-SDK 类型声明
+ * 使用方式:在 index.html 中引入 <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
+ */
+
+declare global {
+  interface Window {
+    wx: WeixinJSBridge
+  }
+}
+
+interface WeixinJSBridge {
+  /**
+   * 注入权限验证配置
+   */
+  config(config: {
+    debug?: boolean
+    appId: string
+    timestamp: number
+    nonceStr: string
+    signature: string
+    jsApiList: string[]
+    openTagList?: string[]
+  }): void
+
+  /**
+   * 验证成功回调
+   */
+  ready(callback: () => void): void
+
+  /**
+   * 验证失败回调
+   */
+  error(callback: (res: any) => void): void
+}
+
+declare const wx: WeixinJSBridge
+
+export default wx

+ 21 - 0
src/utils/template.ts

@@ -0,0 +1,21 @@
+export function ab2hex(buffer: any) {
+  var hexArr = Array.prototype.map.call(
+    new Uint8Array(buffer),
+    function(bit) {
+      return ('00' + bit.toString(16)).slice(-2)
+    }
+  )
+  return hexArr.join('');
+}
+
+
+
+
+export function  getBeaconInfo(device: any) {
+
+    let bf = device.advertisData.slice(2, 8);
+    let mac = Array.prototype.map
+      .call(new Uint8Array(bf), (x) => ("00" + x.toString(16)).slice(-2))
+      .join(":");
+   return mac.toUpperCase();
+}

+ 13 - 4
src/utils/tools.ts

@@ -1,5 +1,6 @@
 import CryptoJS  from 'crypto-js';
-
+import APPConfig from '@/config/index'
+import { AppTypeEnum } from '@/enum';
 
 export function hexStringToUtf8(hexStr: string): string {
   // 1. 清理十六进制字符串(移除空格、0x前缀等)
@@ -131,12 +132,20 @@ export function manualUtf8Decode(uint8Array: Uint8Array) {
   return str;
 }
 
-const secretKey = 'LHacefhtiGhk6Amhc1ypFrSHmoU8ZE0R'
+
+ 
+const xiadouAIsecretKey = 'LHacefhtiGhk6Amhc1ypFrSHmoU8ZE0R'
+
+const storySecreKey = "eM1_qF5^qF7@dA0!aF1@bA7aA5!iO"
 
 export const generatesSignatureStr = (data?: string) => {
   const timestamp = new Date().getTime()
+  // const finishSecretKey =  APPConfig.appType === AppTypeEnum.StoryMachine ? storySecreKey : xiadouAIsecretKey
+  const finishSecretKey = xiadouAIsecretKey
+  console.log("finishSecretKey=======", finishSecretKey);
+  
   let _SignatureStr;
-  if (data) {
+  if (data) { 
     console.log('有请求体签名');
     
     _SignatureStr = `${timestamp}`+ data
@@ -149,7 +158,7 @@ export const generatesSignatureStr = (data?: string) => {
   
  return {
   timestamp: timestamp,
-  signature: CryptoJS.HmacSHA256(_SignatureStr, secretKey).toString(CryptoJS.enc.Hex)
+  signature: CryptoJS.HmacSHA256(_SignatureStr, finishSecretKey).toString(CryptoJS.enc.Hex)
  }
 }
 

+ 92 - 0
upload.py

@@ -0,0 +1,92 @@
+import paramiko
+import os
+import sys
+import time
+import zipfile
+
+hostname="000.luojigou.vip"
+username="root"
+password='XDgP&^cTNs4SGk#r*O9^'
+
+port=222
+############################## 
+############################## 
+############################## 
+
+transport = paramiko.Transport((hostname, port))
+
+transport.connect(username=username, password=password)
+
+############################## 
+############################## 
+############################## 
+
+sftp = paramiko.SFTPClient.from_transport(transport)
+
+# transport.close(),
+############################## 
+############################## 
+############################## 
+
+# 创建SSH对象
+ssh = paramiko.SSHClient()
+
+# 允许连接不在know_hosts文件中的主机
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+# 连接服务器
+ssh.connect(hostname=hostname, port=port, username=username, password=password,allow_agent=False,look_for_keys=False)
+
+############################## 
+##############################
+############################## 
+
+local_path = os.getcwd() + "\dist\/build\h5.zip"
+
+target_file_name = sys.argv[2]
+
+remote_path = sys.argv[1]
+
+remote_file_name = remote_path + target_file_name
+
+print("本地文件地址: " + local_path )
+print("远程目录地址: " + remote_file_name)
+
+
+srmdir_all_folder = os.getcwd() + '/dist/build/h5'  # 文件夹路径
+
+zip_file_path = srmdir_all_folder + ".zip"  # 压缩文件路径
+
+print("打包中.....🍗")
+def make_zip(source_dir, output_filename):
+    zipf = zipfile.ZipFile(output_filename, 'w')    
+    pre_len = len(os.path.dirname(source_dir))
+    for parent, _, filenames in os.walk(source_dir):
+        for filename in filenames:
+            pathfile = os.path.join(parent, filename)
+            arcname = pathfile[pre_len:].strip(os.path.sep)     #相对路径
+            zipf.write(pathfile, arcname.replace('/h5', ''))
+    zipf.close()
+
+make_zip(srmdir_all_folder, zip_file_path)
+
+time.sleep(1)
+print("打包完成.....😎")
+
+print("上传中.....💪")
+sftp.put(local_path,  remote_file_name + "/h5.zip")
+
+time.sleep(1)
+
+cmd1 = "unzip -o -d " + remote_file_name + " " + remote_file_name + "/h5.zip"
+
+stdin, stdout, stderr= ssh.exec_command(cmd1)
+
+result = stdout.read()
+
+print("上传成功🎉🎉🎉🎉")
+
+transport.close()
+
+ssh.close()
+
+

+ 9 - 1
vite.config.ts

@@ -5,5 +5,13 @@ import uni from "@dcloudio/vite-plugin-uni";
 export default defineConfig({
   plugins: [
     uni()
-  ]
+  ],
+  // H5 打包配置
+  base: '/g/',
+  build: {
+    // 输出目录
+    outDir: 'dist/build/h5',
+    // 静态资源目录
+    assetsDir: 'static'
+  }
 });

File diff suppressed because it is too large
+ 453 - 296
yarn.lock


+ 102 - 0
蓝牙广播包需求文档.md

@@ -0,0 +1,102 @@
+# 蓝牙广播包改造需求
+
+## 背景
+
+当前设备二维码中存储的是设备 MAC 地址(格式:`98:A3:16:35:D2:52`)。
+
+在 **Android** 平台,系统 BLE API 的 `deviceId` 直接就是 MAC 地址,可以通过扫码结果直接发起连接。
+
+在 **iOS** 平台,系统出于隐私保护,不暴露设备的真实 MAC 地址,`deviceId` 是系统随机分配的 UUID,且每次重置蓝牙后会变化。因此 iOS 无法通过 MAC 地址直接连接设备,**必须先扫描周边设备,再通过某种标识从广播包中识别出目标设备**。
+
+## 当前问题
+
+当前设备广播包内容(十六进制):
+
+```
+06000109210AD04EB83117A450432D323032333032313531333539
+```
+
+其中不包含设备 MAC 地址,导致 iOS 端扫码后无法定位到目标设备。
+
+## 需求
+
+请在 BLE 广播包的 **Manufacturer Specific Data(厂商自定义数据,AD Type = 0xFF)** 字段中,加入设备的 **MAC 地址(6字节,大端序)**。
+
+### 广播包格式要求
+
+```
+[Length] [Type=0xFF] [Company ID Low] [Company ID High] [MAC 6字节, 大端序] [其他自定义数据...]
+```
+
+### 示例
+
+设备 MAC 为 `98:A3:16:35:D2:52`,对应字节为:
+
+```
+98 A3 16 35 D2 52
+```
+
+厂商数据段示例(Company ID 使用 0x0000 占位,可替换为实际注册 ID):
+
+```
+[Length] FF 00 00 98 A3 16 35 D2 52 [其他数据...]
+```
+
+完整广播包示例(在现有数据基础上增加该字段):
+
+```
+原有数据段保持不变
+新增字段: 0A FF 00 00 98 A3 16 35 D2 52
+```
+
+> `0A` = 后续字节长度(10字节:Type 1字节 + Company ID 2字节 + MAC 6字节 + 预留1字节)
+
+### 字节序说明
+
+MAC 地址按**大端序**写入,即 `98:A3:16:35:D2:52` 写为:
+
+```
+98 A3 16 35 D2 52
+```
+
+不要使用小端序(`52 D2 35 16 A3 98`),以便 App 端直接比对。
+
+## App 端解析逻辑
+
+App 收到广播包后,会遍历所有 AD Structure,找到 `Type = 0xFF` 的字段,从第 3 字节(跳过 Company ID)开始读取 6 字节作为 MAC,与扫码结果比对:
+
+```typescript
+// 解析 Manufacturer Specific Data 中的 MAC
+function extractMacFromAdvertData(advertisData: ArrayBuffer): string {
+  const bytes = new Uint8Array(advertisData)
+  let i = 0
+  while (i < bytes.length) {
+    const len = bytes[i]
+    const type = bytes[i + 1]
+    if (type === 0xFF && len >= 8) {
+      // 跳过 length(1) + type(1) + companyId(2),取后6字节为MAC
+      const mac = Array.from(bytes.slice(i + 4, i + 10))
+        .map(b => b.toString(16).padStart(2, '0').toUpperCase())
+        .join('')
+      return mac  // 返回格式如 "98A31635D252"
+    }
+    i += len + 1
+  }
+  return ''
+}
+```
+
+比对时将扫码 MAC `98:A3:16:35:D2:52` 去掉冒号转大写后与上述结果直接比较。
+
+## 验证方法
+
+修改固件后,可使用以下工具验证广播包内容:
+
+- **iOS**:[nRF Connect for Mobile](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-mobile)
+- **Android**:nRF Connect 或 BLE Scanner
+
+在 nRF Connect 中找到目标设备,展开 **Raw Advertising Data**,确认 Manufacturer Specific Data 字段中包含正确的 MAC 字节。
+
+## 联系人
+
+如有疑问请联系 App 开发团队。

Some files were not shown because too many files changed in this diff