FEAT => 更新测试页面URL,移除不再使用的postMessage测试组件,优化项目配置,添加Capacitor命令到package.json。

This commit is contained in:
超级老白兔
2025-07-31 18:44:45 +08:00
parent ecddbf2226
commit efec6d6857
10 changed files with 223 additions and 1298 deletions

133
nkebao/APP打包方案.md Normal file
View File

@@ -0,0 +1,133 @@
# Cunkebao APP 打包方案
## 方案一Capacitor推荐
Capacitor 是 Ionic 团队开发的跨平台原生应用打包工具,可以将 React 项目打包成原生 Android/iOS 应用。
### 安装步骤
```bash
# 1. 安装 Capacitor
npm install @capacitor/core @capacitor/cli
# 2. 初始化 Capacitor
npx cap init
# 3. 添加平台
npm run cap:add android
npm run cap:add ios
```
### 构建流程
```bash
# 1. 构建 React 项目
npm run build
# 2. 复制构建文件到原生项目
npm run cap:copy
# 3. 同步依赖
npm run cap:sync
# 4. 打开原生开发环境
npm run cap:open:android # Android Studio
npm run cap:open:ios # Xcode
```
### 优势
- 真正的原生应用体验
- 支持原生 API 调用
- 性能优秀
- 支持热更新
---
## 方案二PWA渐进式 Web 应用)
将项目打包成 PWA用户可以通过浏览器安装到桌面。
### 安装 Workbox
```bash
npm install workbox-webpack-plugin
```
### 配置 PWA
`vite.config.ts` 中添加 PWA 插件配置。
### 优势
- 无需应用商店审核
- 跨平台兼容性好
- 更新方便
---
## 方案三Tauri
Tauri 使用 Rust 后端,可以创建更轻量级的桌面应用。
### 安装 Tauri
```bash
npm install @tauri-apps/cli
npm install @tauri-apps/api
```
### 初始化
```bash
npx tauri init
```
### 优势
- 应用体积小
- 性能优秀
- 安全性高
---
## 方案四Electron
传统的桌面应用打包方案。
### 安装 Electron
```bash
npm install electron electron-builder --save-dev
```
### 优势
- 生态成熟
- 文档丰富
- 社区支持好
---
## 推荐方案
对于你的移动端项目,推荐使用 **Capacitor**
1. **最适合移动端**:你的项目使用了 antd-mobile专门为移动端设计
2. **原生体验**:可以获得真正的原生应用体验
3. **跨平台**:一套代码可以打包成 Android 和 iOS 应用
4. **性能优秀**:比 WebView 方案性能更好
### 快速开始
1. 安装 Capacitor 依赖
2. 运行 `npm run cap:build:android``npm run cap:build:ios`
3. 使用 Android Studio 或 Xcode 打开生成的项目
4. 构建并运行应用
### 注意事项
- Android 需要安装 Android Studio 和 Android SDK
- iOS 需要 macOS 系统和 Xcode
- 确保 `capacitor.config.ts` 中的配置正确
- 可能需要根据实际需求调整应用图标、启动画面等

View File

@@ -0,0 +1,23 @@
import { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.cunkebao.app",
appName: "Cunkebao",
webDir: "dist",
server: {
androidScheme: "https",
},
plugins: {
SplashScreen: {
launchShowDuration: 2000,
backgroundColor: "#ffffff",
showSpinner: true,
spinnerColor: "#999999",
},
StatusBar: {
style: "dark",
},
},
};
export default config;

View File

@@ -3,6 +3,7 @@
"version": "3.0.0",
"license": "MIT",
"private": true,
"homepage": "./",
"dependencies": {
"@ant-design/icons": "^5.6.1",
"antd": "^5.13.1",
@@ -44,6 +45,13 @@
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,scss,css}\"",
"lint:check": "eslint src --ext .js,.jsx,.ts,.tsx",
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,scss,css}\""
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,scss,css}\"",
"cap:add": "cap add",
"cap:copy": "cap copy",
"cap:sync": "cap sync",
"cap:open:android": "cap open android",
"cap:open:ios": "cap open ios",
"cap:build:android": "npm run build && cap copy && cap sync",
"cap:build:ios": "npm run build && cap copy && cap sync"
}
}

View File

@@ -78,16 +78,6 @@ const Mine: React.FC = () => {
bgColor: "#fff7e6",
iconColor: "#fa8c16",
},
{
id: "test",
title: "测试",
description: "测试",
icon: <MessageOutlined />,
count: 0,
path: "/test/postMessage",
bgColor: "#fff7e6",
iconColor: "#fa8c16",
},
];
// 加载统计数据

View File

@@ -1,646 +0,0 @@
import React, { useState, useEffect } from "react";
import {
Button,
Card,
Space,
Divider,
Typography,
Alert,
Tag,
Avatar,
Row,
Col,
} from "antd";
import {
UserOutlined,
MobileOutlined,
MessageOutlined,
ExclamationCircleOutlined,
CheckCircleOutlined,
ShareAltOutlined,
PayCircleOutlined,
SendOutlined,
InfoCircleOutlined,
LoadingOutlined,
} from "@ant-design/icons";
import bridge, {
ResponseType,
type UserInfo,
type DeviceInfo,
type ShareData,
type PaymentData,
} from "@/utils/postMessage";
import Layout from "@/components/Layout/Layout";
import NavCommon from "@/components/NavCommon";
const { Text, Paragraph } = Typography;
const PostMessageTest: React.FC = () => {
const [isReady, setIsReady] = useState(false);
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const [deviceInfo, setDeviceInfo] = useState<DeviceInfo | null>(null);
const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState<string[]>([]);
const [lastResult, setLastResult] = useState<any>(null);
// 添加日志
const addLog = (message: string) => {
const timestamp = new Date().toLocaleTimeString();
setLogs(prev => [`[${timestamp}] ${message}`, ...prev.slice(0, 19)]);
};
// 初始化桥接
useEffect(() => {
const initBridge = async () => {
try {
addLog("正在初始化UniApp桥接...");
// 立即检查桥接状态
const initialStatus = bridge.getBridgeStatus();
addLog(`初始桥接状态: ${JSON.stringify(initialStatus, null, 2)}`);
// 设置超时处理
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("桥接初始化超时 (30秒)")), 30000);
});
// 等待桥接就绪,带超时
await Promise.race([bridge.waitForReady(), timeoutPromise]);
setIsReady(true);
addLog("✅ UniApp桥接初始化成功");
// 设置监听器
bridge.on(ResponseType.USER_INFO, (data: UserInfo) => {
setUserInfo(data);
addLog(`收到用户信息: ${data.name}`);
});
bridge.on(ResponseType.DEVICE_INFO, (data: DeviceInfo) => {
setDeviceInfo(data);
addLog(`收到设备信息: ${data.platform} ${data.model}`);
});
bridge.on(ResponseType.SHARE_RESULT, (result: any) => {
setLastResult(result);
if (result.success) {
addLog("✅ 分享成功");
} else {
addLog(`❌ 分享失败: ${result.error}`);
}
});
bridge.on(ResponseType.PAYMENT_RESULT, (result: any) => {
setLastResult(result);
if (result.success) {
addLog(`✅ 支付成功订单ID: ${result.orderId}`);
} else {
addLog(`❌ 支付失败: ${result.error}`);
}
});
bridge.on(ResponseType.CONFIRM_RESULT, (result: any) => {
setLastResult(result);
addLog(`用户${result.confirmed ? "确认" : "取消"}了操作`);
});
// 通知页面准备就绪
bridge.notifyPageReady({
version: "1.0.0",
features: ["postMessage", "bridge"],
});
addLog("已通知页面准备就绪");
// 延迟后再次检查状态
setTimeout(() => {
const finalStatus = bridge.getBridgeStatus();
addLog(`最终桥接状态: ${JSON.stringify(finalStatus, null, 2)}`);
}, 2000);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
addLog(`❌ 桥接初始化失败: ${errorMessage}`);
// 提供重试建议
addLog("💡 建议检查:");
addLog(" 1. 是否在UniApp WebView环境中运行");
addLog(" 2. 网络连接是否正常");
addLog(" 3. 尝试刷新页面重试");
addLog(" 4. 检查UniApp端控制台日志");
}
};
initBridge();
return () => {
bridge.destroy();
};
}, []);
// 获取用户信息
const handleGetUserInfo = async () => {
setLoading(true);
try {
bridge.getUserInfo();
addLog("已请求用户信息");
} catch (error) {
addLog(`获取用户信息失败: ${error}`);
} finally {
setLoading(false);
}
};
// 获取设备信息
const handleGetDeviceInfo = async () => {
setLoading(true);
try {
bridge.getDeviceInfo();
addLog("已请求设备信息");
} catch (error) {
addLog(`获取设备信息失败: ${error}`);
} finally {
setLoading(false);
}
};
// 显示Toast
const handleShowToast = async () => {
try {
bridge.showToast("这是一个测试Toast消息", 3000);
addLog("已显示Toast消息");
} catch (error) {
addLog(`显示Toast失败: ${error}`);
}
};
// 显示Alert
const handleShowAlert = async () => {
try {
bridge.showAlert("测试提示", "这是一个测试Alert对话框");
addLog("已显示Alert对话框");
} catch (error) {
addLog(`显示Alert失败: ${error}`);
}
};
// 显示Confirm
const handleShowConfirm = async () => {
setLoading(true);
try {
const confirmed = await bridge.showConfirm(
"确认操作",
"确定要执行此测试操作吗?",
);
setLastResult({ confirmed });
if (confirmed) {
addLog("用户确认了操作");
} else {
addLog("用户取消了操作");
}
} catch (error) {
addLog(`显示Confirm失败: ${error}`);
} finally {
setLoading(false);
}
};
// 分享内容
const handleShare = async () => {
setLoading(true);
try {
const shareData: ShareData = {
title: "UniApp桥接测试",
content: "这是一个UniApp WebView桥接功能的测试页面",
url: window.location.href,
image: "https://via.placeholder.com/300x200/4CAF50/FFFFFF?text=Test",
};
const success = await bridge.share(shareData);
setLastResult({ success });
if (success) {
addLog("分享成功");
} else {
addLog("分享失败");
}
} catch (error) {
addLog(`分享失败: ${error}`);
} finally {
setLoading(false);
}
};
// 支付测试
const handlePayment = async () => {
setLoading(true);
try {
const paymentData: PaymentData = {
amount: 100,
orderId: "test_order_" + Date.now(),
description: "测试支付",
currency: "CNY",
};
const success = await bridge.payment(paymentData);
setLastResult({ success, orderId: paymentData.orderId });
if (success) {
addLog(`支付成功订单ID: ${paymentData.orderId}`);
} else {
addLog("支付失败");
}
} catch (error) {
addLog(`支付失败: ${error}`);
} finally {
setLoading(false);
}
};
// 发送自定义消息
const handleCustomMessage = async () => {
try {
bridge.sendCustomMessage("testAction", {
action: "test",
timestamp: Date.now(),
data: { message: "Hello from React!" },
});
addLog("已发送自定义消息");
} catch (error) {
addLog(`发送自定义消息失败: ${error}`);
}
};
// 页面导航
const handleNavigate = async () => {
try {
bridge.navigate("https://example.com");
addLog("已请求页面导航");
} catch (error) {
addLog(`页面导航失败: ${error}`);
}
};
return (
<Layout header={<NavCommon title="UniApp桥接测试" />}>
<div style={{ padding: "20px" }}>
{/* 状态指示器 */}
<Alert
message={
<Space>
{isReady ? (
<>
<CheckCircleOutlined style={{ color: "#52c41a" }} />
<Text strong></Text>
</>
) : (
<>
<LoadingOutlined />
<Text>...</Text>
</>
)}
</Space>
}
type={isReady ? "success" : "info"}
showIcon={false}
style={{ marginBottom: "20px" }}
/>
<Row gutter={[20, 20]}>
{/* 左侧:功能测试 */}
<Col xs={24} lg={12}>
<Card title="功能测试" size="small">
<Space direction="vertical" style={{ width: "100%" }}>
<Button
type="primary"
icon={<UserOutlined />}
onClick={handleGetUserInfo}
loading={loading}
block
>
</Button>
<Button
type="primary"
icon={<MobileOutlined />}
onClick={handleGetDeviceInfo}
loading={loading}
block
>
</Button>
<Divider />
<Button
icon={<MessageOutlined />}
onClick={handleShowToast}
block
>
Toast
</Button>
<Button
icon={<ExclamationCircleOutlined />}
onClick={handleShowAlert}
block
>
Alert
</Button>
<Button
icon={<CheckCircleOutlined />}
onClick={handleShowConfirm}
loading={loading}
block
>
Confirm
</Button>
<Divider />
<Button
icon={<ShareAltOutlined />}
onClick={handleShare}
loading={loading}
block
>
</Button>
<Button
icon={<PayCircleOutlined />}
onClick={handlePayment}
loading={loading}
block
>
</Button>
<Divider />
<Button
icon={<SendOutlined />}
onClick={handleCustomMessage}
block
>
</Button>
<Button
icon={<InfoCircleOutlined />}
onClick={handleNavigate}
block
>
</Button>
<Divider />
<Button
icon={<InfoCircleOutlined />}
onClick={() => {
bridge.checkBridgeStatus();
const status = bridge.getBridgeStatus();
addLog(`桥接状态: ${JSON.stringify(status, null, 2)}`);
}}
block
>
</Button>
<Button
icon={<SendOutlined />}
onClick={() => {
// 测试直接发送消息
try {
if (window.uniAppBridge) {
window.uniAppBridge.postMessage("test", {
message: "测试消息",
});
addLog("✅ 直接发送测试消息成功");
} else {
addLog("❌ window.uniAppBridge 不存在");
}
} catch (error) {
addLog(`❌ 直接发送消息失败: ${error}`);
}
}}
block
>
</Button>
<Button
icon={<InfoCircleOutlined />}
onClick={() => {
// 测试监听器状态
const status = bridge.getBridgeStatus();
addLog(`监听器数量: ${status.listenersCount}`);
addLog(`消息队列长度: ${status.messageQueueLength}`);
addLog(`桥接就绪: ${status.isReady}`);
addLog(`桥接存在: ${status.bridgeExists}`);
}}
block
>
</Button>
<Button
icon={<LoadingOutlined />}
onClick={() => {
// 强制重新初始化桥接
addLog("🔄 强制重新初始化桥接...");
bridge.destroy();
// 重新创建桥接实例
setTimeout(() => {
try {
// 重新初始化
bridge
.waitForReady()
.then(() => {
setIsReady(true);
addLog("✅ 强制重新初始化成功");
})
.catch(error => {
addLog(`❌ 强制重新初始化失败: ${error}`);
});
} catch (error) {
addLog(`❌ 强制重新初始化异常: ${error}`);
}
}, 1000);
}}
block
>
</Button>
<Button
icon={<InfoCircleOutlined />}
onClick={() => {
// 环境检测
addLog("🔍 环境检测开始...");
addLog(`用户代理: ${navigator.userAgent}`);
addLog(`当前URL: ${window.location.href}`);
addLog(`父窗口: ${!!window.parent}`);
addLog(`顶层窗口: ${!!window.top}`);
addLog(`是否在iframe中: ${window !== window.top}`);
addLog(`window.uniAppBridge: ${!!window.uniAppBridge}`);
// 检查是否在UniApp环境中
const isInUniApp =
navigator.userAgent.includes("uni-app") ||
window.location.href.includes("uni-app") ||
!!window.uniAppBridge;
addLog(`是否在UniApp环境: ${isInUniApp}`);
// 检查平台信息
if (window.uniAppBridge) {
addLog("✅ 桥接已存在,可以正常通信");
} else {
addLog("❌ 桥接不存在可能不在UniApp环境中");
}
}}
block
>
</Button>
</Space>
</Card>
</Col>
{/* 右侧:信息显示 */}
<Col xs={24} lg={12}>
{/* 用户信息 */}
{userInfo && (
<Card
title="用户信息"
size="small"
style={{ marginBottom: "20px" }}
>
<Space>
<Avatar src={userInfo.avatar} icon={<UserOutlined />} />
<div>
<div>
<Text strong>{userInfo.name}</Text>
</div>
<div>
<Text type="secondary">ID: {userInfo.id}</Text>
</div>
{userInfo.phone && (
<div>
<Text type="secondary">: {userInfo.phone}</Text>
</div>
)}
{userInfo.email && (
<div>
<Text type="secondary">: {userInfo.email}</Text>
</div>
)}
</div>
</Space>
</Card>
)}
{/* 设备信息 */}
{deviceInfo && (
<Card
title="设备信息"
size="small"
style={{ marginBottom: "20px" }}
>
<Space direction="vertical" style={{ width: "100%" }}>
<div>
<Text strong>:</Text>{" "}
<Tag color="blue">{deviceInfo.platform}</Tag>
</div>
<div>
<Text strong>:</Text> <Text>{deviceInfo.model}</Text>
</div>
<div>
<Text strong>:</Text> <Text>{deviceInfo.version}</Text>
</div>
<div>
<Text strong>:</Text>{" "}
<Text>
{deviceInfo.appName} v{deviceInfo.appVersion}
</Text>
</div>
<div>
<Text strong>:</Text>{" "}
<Text>
{deviceInfo.screenWidth} × {deviceInfo.screenHeight}
</Text>
</div>
</Space>
</Card>
)}
{/* 最后结果 */}
{lastResult && (
<Card
title="最后操作结果"
size="small"
style={{ marginBottom: "20px" }}
>
<pre style={{ fontSize: "12px", margin: 0 }}>
{JSON.stringify(lastResult, null, 2)}
</pre>
</Card>
)}
</Col>
</Row>
{/* 日志区域 */}
<Card title="操作日志" size="small" style={{ marginTop: "20px" }}>
<div
style={{
height: "200px",
overflowY: "auto",
backgroundColor: "#f5f5f5",
padding: "10px",
borderRadius: "4px",
fontFamily: "monospace",
fontSize: "12px",
}}
>
{logs.length === 0 ? (
<Text type="secondary"></Text>
) : (
logs.map((log, index) => (
<div key={index} style={{ marginBottom: "4px" }}>
{log}
</div>
))
)}
</div>
</Card>
{/* 使用说明 */}
<Card title="使用说明" size="small" style={{ marginTop: "20px" }}>
<Paragraph>
<Text strong></Text>
</Paragraph>
<ul>
<li>
<Text>UniApp WebView桥接功能</Text>
</li>
<li>
<Text></Text>
</li>
<li>
<Text></Text>
</li>
<li>
<Text></Text>
</li>
<li>
<Text>UniApp WebView环境中使用以获得最佳效果</Text>
</li>
</ul>
</Card>
</div>
</Layout>
);
};
export default PostMessageTest;

View File

@@ -16,11 +16,6 @@ const componentTestRoutes = DEV_FEATURES.SHOW_TEST_PAGES
element: <SelectTest />,
auth: true,
},
{
path: "/test/postMessage",
element: <PostMessageTest />,
auth: true,
},
]
: [];

View File

@@ -1,634 +0,0 @@
// UniApp WebView 桥接工具类
import { DEV_FEATURES } from "./env";
export interface BridgeMessage {
type: string;
data: any;
timestamp: number;
}
export interface UserInfo {
id: string;
name: string;
avatar: string;
phone?: string;
email?: string;
}
export interface DeviceInfo {
platform: string;
model: string;
version: string;
appVersion: string;
appName: string;
screenWidth: number;
screenHeight: number;
statusBarHeight: number;
}
export interface ShareData {
title: string;
content: string;
url: string;
image?: string;
}
export interface PaymentData {
amount: number;
orderId: string;
description?: string;
currency?: string;
}
export interface ToastData {
message: string;
duration?: number;
}
export interface AlertData {
title: string;
content: string;
}
export interface ConfirmData {
title: string;
content: string;
}
// 消息回调函数类型
export type MessageCallback = (data: any) => void;
// 消息类型枚举
export enum MessageType {
GET_USER_INFO = "getUserInfo",
GET_DEVICE_INFO = "getDeviceInfo",
TOAST = "toast",
ALERT = "alert",
CONFIRM = "confirm",
SHARE = "share",
PAYMENT = "payment",
NAVIGATE = "navigate",
PAGE_LOADED = "pageLoaded",
PAGE_READY = "pageReady",
CUSTOM = "custom",
}
// 响应消息类型
export enum ResponseType {
USER_INFO = "userInfo",
DEVICE_INFO = "deviceInfo",
SHARE_RESULT = "shareResult",
PAYMENT_RESULT = "paymentResult",
CONFIRM_RESULT = "confirmResult",
NAVIGATE_RESULT = "navigateResult",
}
class UniAppBridge {
private messageQueue: BridgeMessage[] = [];
private isReady = false;
private listeners: Map<string, MessageCallback[]> = new Map();
private bridgeCheckInterval: number | null = null;
constructor() {
(this as any)._startTime = Date.now();
this.initBridge();
}
/**
* 初始化桥接
*/
private initBridge(): void {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("🔧 开始初始化UniApp桥接");
console.log("🔧 当前环境:", {
userAgent: navigator.userAgent,
location: window.location.href,
windowParent: !!window.parent,
windowTop: !!window.top,
});
}
// 检查桥接是否已存在
if (window.uniAppBridge) {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("✅ 桥接已存在,直接使用");
}
this.isReady = true;
this.setupMessageListener();
return;
}
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("⏳ 桥接不存在,开始等待注入");
}
// 等待桥接注入
this.waitForBridge();
}
/**
* 等待桥接注入
*/
private waitForBridge(): void {
let retryCount = 0;
const maxRetries = 3;
const checkInterval = 200; // 增加检查间隔到200ms
const timeoutDuration = 30000; // 增加到30秒超时
this.bridgeCheckInterval = window.setInterval(() => {
if (window.uniAppBridge) {
this.isReady = true;
this.setupMessageListener();
if (this.bridgeCheckInterval) {
clearInterval(this.bridgeCheckInterval);
this.bridgeCheckInterval = null;
}
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("✅ UniApp桥接已就绪");
}
return;
}
// 增加重试逻辑
retryCount++;
if (retryCount % 50 === 0) {
// 每10秒输出一次日志
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log(
`⏳ 等待UniApp桥接注入... (${(retryCount * checkInterval) / 1000}s)`,
);
}
}
}, checkInterval);
// 设置超时
setTimeout(() => {
if (this.bridgeCheckInterval) {
clearInterval(this.bridgeCheckInterval);
this.bridgeCheckInterval = null;
console.warn("❌ UniApp桥接初始化超时 (30秒)");
// 尝试手动触发桥接注入
this.tryManualBridgeInjection();
}
}, timeoutDuration);
}
/**
* 尝试手动触发桥接注入
*/
private tryManualBridgeInjection(): void {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("🔄 尝试手动触发桥接注入...");
}
// 发送页面准备就绪消息可能触发UniApp重新注入桥接
try {
window.parent.postMessage(
{
type: "pageReady",
data: {
url: window.location.href,
timestamp: Date.now(),
retry: true,
},
},
"*",
);
} catch (error) {
console.error("手动触发桥接注入失败:", error);
}
// 延迟后重新检查
setTimeout(() => {
if (window.uniAppBridge) {
this.isReady = true;
this.setupMessageListener();
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("✅ 手动触发后桥接已就绪");
}
} else {
console.error("❌ 手动触发后桥接仍未就绪");
}
}, 2000);
}
/**
* 设置消息监听器
*/
private setupMessageListener(): void {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("🔧 设置UniApp消息监听器");
}
window.addEventListener("uniAppMessage", (event: any) => {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📨 收到uniAppMessage事件:", event);
}
try {
const { type, data } = event.detail;
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📨 解析消息:", type, data);
}
this.handleMessage(type, data);
} catch (error) {
console.error("❌ 处理uniAppMessage事件失败:", error);
}
});
// 同时监听原生message事件作为备用
window.addEventListener("message", (event: any) => {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📨 收到原生message事件:", event.data);
}
try {
if (event.data && event.data.type) {
const { type, data } = event.data;
// 处理注入代码消息
if (type === "injectCode") {
this.handleInjectCode(data);
return;
}
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📨 解析原生消息:", type, data);
}
this.handleMessage(type, data);
}
} catch (error) {
console.error("❌ 处理原生message事件失败:", error);
}
});
}
/**
* 处理注入代码消息
*/
private handleInjectCode(data: any): void {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("🔧 处理注入代码消息:", data);
}
try {
if (data.code) {
// 执行注入的代码
const script = document.createElement("script");
script.textContent = data.code;
document.head.appendChild(script);
document.head.removeChild(script);
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("✅ 代码注入成功");
}
}
} catch (error) {
console.error("❌ 代码注入失败:", error);
}
}
/**
* 处理接收到的消息
*/
private handleMessage(type: string, data: any): void {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📨 处理UniApp消息:", type, data);
}
// 触发对应的事件监听器
if (this.listeners.has(type)) {
const callbacks = this.listeners.get(type);
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log(`📨 触发${type}事件监听器, 回调数量:`, callbacks?.length);
}
callbacks?.forEach((callback, index) => {
try {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log(`📨 执行回调${index + 1}:`, callback);
}
callback(data);
} catch (error) {
console.error(`❌ 处理消息回调失败 (${type}):`, error);
}
});
} else {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log(`📨 未找到${type}事件的监听器`);
}
}
}
/**
* 发送消息到UniApp
*/
private postMessage(type: string, data: any = {}): void {
if (!this.isReady) {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.warn("⚠️ 桥接未就绪,消息已加入队列:", type, data);
}
this.messageQueue.push({
type,
data,
timestamp: Date.now(),
});
return;
}
try {
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("📤 发送消息到UniApp:", type, data);
}
if (!window.uniAppBridge) {
console.error("❌ window.uniAppBridge 不存在");
return;
}
if (!window.uniAppBridge.postMessage) {
console.error("❌ window.uniAppBridge.postMessage 方法不存在");
return;
}
window.uniAppBridge.postMessage(type, data);
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("✅ 消息发送成功");
}
} catch (error) {
console.error("❌ 发送消息失败:", error);
}
}
/**
* 处理消息队列
*/
private processMessageQueue(): void {
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
if (message) {
this.postMessage(message.type, message.data);
}
}
}
/**
* 添加消息监听器
*/
public on(type: string, callback: MessageCallback): void {
if (!this.listeners.has(type)) {
this.listeners.set(type, []);
}
this.listeners.get(type)?.push(callback);
}
/**
* 移除消息监听器
*/
public off(type: string, callback?: MessageCallback): void {
if (!callback) {
this.listeners.delete(type);
} else {
const callbacks = this.listeners.get(type);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
}
/**
* 获取用户信息
*/
public getUserInfo(): void {
this.postMessage(MessageType.GET_USER_INFO);
}
/**
* 获取设备信息
*/
public getDeviceInfo(): void {
this.postMessage(MessageType.GET_DEVICE_INFO);
}
/**
* 显示Toast提示
*/
public showToast(message: string, duration: number = 2000): void {
this.postMessage(MessageType.TOAST, { message, duration });
}
/**
* 显示Alert对话框
*/
public showAlert(title: string, content: string): void {
this.postMessage(MessageType.ALERT, { title, content });
}
/**
* 显示Confirm确认框
*/
public showConfirm(title: string, content: string): Promise<boolean> {
return new Promise(resolve => {
const handleConfirmResult = (data: any) => {
this.off(ResponseType.CONFIRM_RESULT, handleConfirmResult);
resolve(data.confirmed || false);
};
this.on(ResponseType.CONFIRM_RESULT, handleConfirmResult);
this.postMessage(MessageType.CONFIRM, { title, content });
});
}
/**
* 分享内容
*/
public share(data: ShareData): Promise<boolean> {
return new Promise(resolve => {
const handleShareResult = (result: any) => {
this.off(ResponseType.SHARE_RESULT, handleShareResult);
resolve(result.success || false);
};
this.on(ResponseType.SHARE_RESULT, handleShareResult);
this.postMessage(MessageType.SHARE, data);
});
}
/**
* 支付
*/
public payment(data: PaymentData): Promise<boolean> {
return new Promise(resolve => {
const handlePaymentResult = (result: any) => {
this.off(ResponseType.PAYMENT_RESULT, handlePaymentResult);
resolve(result.success || false);
};
this.on(ResponseType.PAYMENT_RESULT, handlePaymentResult);
this.postMessage(MessageType.PAYMENT, data);
});
}
/**
* 页面导航
*/
public navigate(url: string): void {
this.postMessage(MessageType.NAVIGATE, { url });
}
/**
* 发送自定义消息
*/
public sendCustomMessage(type: string, data: any): void {
this.postMessage(type, data);
}
/**
* 通知页面已准备就绪
*/
public notifyPageReady(data?: any): void {
this.postMessage(MessageType.PAGE_READY, {
title: document.title,
url: window.location.href,
...data,
});
}
/**
* 检查桥接是否就绪
*/
public isBridgeReady(): boolean {
return this.isReady;
}
/**
* 等待桥接就绪
*/
public waitForReady(): Promise<void> {
return new Promise((resolve, reject) => {
if (this.isReady) {
resolve();
return;
}
const maxWaitTime = 30000; // 30秒最大等待时间
const checkInterval = 200; // 200ms检查间隔
let elapsedTime = 0;
const checkReady = () => {
if (this.isReady) {
resolve();
return;
}
elapsedTime += checkInterval;
if (elapsedTime >= maxWaitTime) {
reject(new Error(`桥接初始化超时 (${maxWaitTime / 1000}秒)`));
return;
}
setTimeout(checkReady, checkInterval);
};
checkReady();
});
}
/**
* 销毁桥接
*/
public destroy(): void {
if (this.bridgeCheckInterval) {
clearInterval(this.bridgeCheckInterval);
this.bridgeCheckInterval = null;
}
this.listeners.clear();
this.messageQueue = [];
this.isReady = false;
}
/**
* 获取桥接状态信息
*/
public getBridgeStatus(): {
isReady: boolean;
bridgeExists: boolean;
messageQueueLength: number;
listenersCount: number;
uptime: number;
} {
return {
isReady: this.isReady,
bridgeExists: !!window.uniAppBridge,
messageQueueLength: this.messageQueue.length,
listenersCount: this.listeners.size,
uptime: Date.now() - (this as any)._startTime || 0,
};
}
/**
* 手动检查桥接状态
*/
public checkBridgeStatus(): void {
const status = this.getBridgeStatus();
if (DEV_FEATURES.ENABLE_DEBUG_LOGS) {
console.log("🔍 桥接状态检查:", status);
}
if (!status.bridgeExists) {
console.warn("⚠️ window.uniAppBridge 不存在");
}
if (!status.isReady && status.bridgeExists) {
console.warn("⚠️ 桥接存在但未就绪");
}
}
}
// 创建全局桥接实例
const bridge = new UniAppBridge();
// 导出桥接实例和类型
export default bridge;
export { UniAppBridge };
// 导出便捷方法
export const {
getUserInfo,
getDeviceInfo,
showToast,
showAlert,
showConfirm,
share,
payment,
navigate,
sendCustomMessage,
notifyPageReady,
isBridgeReady,
waitForReady,
on,
off,
destroy,
getBridgeStatus,
checkBridgeStatus,
} = bridge;
// 声明全局类型
declare global {
interface Window {
uniAppBridge?: {
postMessage: (type: string, data: any) => void;
getUserInfo: () => void;
getDeviceInfo: () => void;
showToast: (message: string, duration?: number) => void;
showAlert: (title: string, content: string) => void;
showConfirm: (title: string, content: string) => void;
share: (data: ShareData) => void;
payment: (data: PaymentData) => void;
};
}
}

56
nkebao/vite-pwa.config.ts Normal file
View File

@@ -0,0 +1,56 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: "autoUpdate",
workbox: {
globPatterns: ["**/*.{js,css,html,ico,png,svg}"],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\./,
handler: "NetworkFirst",
options: {
cacheName: "api-cache",
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
},
},
},
],
},
manifest: {
name: "Cunkebao",
short_name: "Cunkebao",
description: "Cunkebao Mobile App",
theme_color: "#ffffff",
background_color: "#ffffff",
display: "standalone",
orientation: "portrait",
scope: "/",
start_url: "/",
icons: [
{
src: "favicon.ico",
sizes: "64x64 32x32 24x24 16x16",
type: "image/x-icon",
},
{
src: "pwa-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "pwa-512x512.png",
sizes: "512x512",
type: "image/png",
},
],
},
}),
],
});