feat: 添加 H5 跳转小程序功能
- 后端: 新增 JS-SDK 签名接口和 URL Scheme 生成接口 - 前端: 创建 MiniProgramLauncher 组件,支持环境自适应 - 微信内 H5: 使用 wx-open-launch-weapp 开放标签 - 外部浏览器: 使用 URL Scheme 拉起微信 - PC 端: 显示小程序码引导扫码 - 引入微信 JS-SDK (jweixin-1.6.0.js) - 新增 miniprogramService 服务层封装 API 调用 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
203
src/components/MiniProgramLauncher/WxOpenLaunchWeapp.js
Normal file
203
src/components/MiniProgramLauncher/WxOpenLaunchWeapp.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 微信开放标签封装组件
|
||||
* 用于在微信内 H5 跳转小程序
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Box, Spinner, Text, Button as ChakraButton } from '@chakra-ui/react';
|
||||
import { getJsSdkConfig } from '@services/miniprogramService';
|
||||
|
||||
// 小程序原始 ID
|
||||
const MINIPROGRAM_ORIGINAL_ID = 'gh_fd2fd8dd2fb5';
|
||||
|
||||
/**
|
||||
* 微信开放标签组件
|
||||
* @param {Object} props
|
||||
* @param {string} [props.path] - 小程序页面路径
|
||||
* @param {string} [props.query] - 页面参数
|
||||
* @param {React.ReactNode} props.children - 按钮内容
|
||||
* @param {Function} [props.onLaunch] - 跳转成功回调
|
||||
* @param {Function} [props.onError] - 跳转失败回调
|
||||
* @param {Object} [props.buttonStyle] - 按钮样式
|
||||
*/
|
||||
const WxOpenLaunchWeapp = ({
|
||||
path = '',
|
||||
query = '',
|
||||
children,
|
||||
onLaunch,
|
||||
onError,
|
||||
buttonStyle = {},
|
||||
}) => {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const launchBtnRef = useRef(null);
|
||||
|
||||
// 初始化微信 JS-SDK
|
||||
const initWxSdk = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// 获取当前页面 URL(不含 hash)
|
||||
const currentUrl = window.location.href.split('#')[0];
|
||||
|
||||
// 获取签名配置
|
||||
const config = await getJsSdkConfig(currentUrl);
|
||||
|
||||
if (!config) {
|
||||
throw new Error('获取签名配置失败');
|
||||
}
|
||||
|
||||
// 检查 wx 对象是否存在
|
||||
if (typeof wx === 'undefined') {
|
||||
throw new Error('微信 JS-SDK 未加载');
|
||||
}
|
||||
|
||||
// 配置 wx
|
||||
wx.config({
|
||||
debug: false,
|
||||
appId: config.appId,
|
||||
timestamp: config.timestamp,
|
||||
nonceStr: config.nonceStr,
|
||||
signature: config.signature,
|
||||
jsApiList: config.jsApiList || [],
|
||||
openTagList: config.openTagList || ['wx-open-launch-weapp'],
|
||||
});
|
||||
|
||||
// 监听 ready 事件
|
||||
wx.ready(() => {
|
||||
console.log('[WxOpenLaunchWeapp] wx.ready');
|
||||
setReady(true);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// 监听 error 事件
|
||||
wx.error((err) => {
|
||||
console.error('[WxOpenLaunchWeapp] wx.error:', err);
|
||||
setError(err.errMsg || '初始化失败');
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('[WxOpenLaunchWeapp] initWxSdk error:', err);
|
||||
setError(err.message || '初始化失败');
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initWxSdk();
|
||||
}, [initWxSdk]);
|
||||
|
||||
// 监听开放标签事件
|
||||
useEffect(() => {
|
||||
const btn = launchBtnRef.current;
|
||||
if (!btn) return;
|
||||
|
||||
const handleLaunch = (e) => {
|
||||
console.log('[WxOpenLaunchWeapp] launch success:', e.detail);
|
||||
onLaunch?.(e.detail);
|
||||
};
|
||||
|
||||
const handleError = (e) => {
|
||||
console.error('[WxOpenLaunchWeapp] launch error:', e.detail);
|
||||
onError?.(e.detail);
|
||||
};
|
||||
|
||||
btn.addEventListener('launch', handleLaunch);
|
||||
btn.addEventListener('error', handleError);
|
||||
|
||||
return () => {
|
||||
btn.removeEventListener('launch', handleLaunch);
|
||||
btn.removeEventListener('error', handleError);
|
||||
};
|
||||
}, [ready, onLaunch, onError]);
|
||||
|
||||
// 构建小程序路径
|
||||
const mpPath = query ? `${path}?${query}` : path;
|
||||
|
||||
// 默认按钮样式
|
||||
const defaultButtonStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '12px 24px',
|
||||
backgroundColor: '#07c160',
|
||||
color: '#ffffff',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
...buttonStyle,
|
||||
};
|
||||
|
||||
// 加载中状态
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" py={3}>
|
||||
<Spinner size="sm" mr={2} />
|
||||
<Text fontSize="sm" color="gray.500">正在初始化...</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// 错误状态
|
||||
if (error) {
|
||||
return (
|
||||
<ChakraButton
|
||||
colorScheme="gray"
|
||||
isDisabled
|
||||
width="100%"
|
||||
py={3}
|
||||
>
|
||||
{error}
|
||||
</ChakraButton>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染开放标签
|
||||
if (ready) {
|
||||
// 微信开放标签需要使用纯 HTML 字符串,不支持 JSX
|
||||
const buttonText = typeof children === 'string' ? children : '打开小程序';
|
||||
const htmlContent = `
|
||||
<style>
|
||||
.wx-launch-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: ${defaultButtonStyle.padding};
|
||||
background-color: ${defaultButtonStyle.backgroundColor};
|
||||
color: ${defaultButtonStyle.color};
|
||||
font-size: ${defaultButtonStyle.fontSize};
|
||||
font-weight: ${defaultButtonStyle.fontWeight};
|
||||
border-radius: ${defaultButtonStyle.borderRadius};
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<button class="wx-launch-btn">${buttonText}</button>
|
||||
`;
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<wx-open-launch-weapp
|
||||
ref={launchBtnRef}
|
||||
id="launch-btn"
|
||||
username={MINIPROGRAM_ORIGINAL_ID}
|
||||
path={mpPath}
|
||||
style={{ display: 'block', width: '100%' }}
|
||||
>
|
||||
<script type="text/wxtag-template" dangerouslySetInnerHTML={{ __html: htmlContent }} />
|
||||
</wx-open-launch-weapp>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default WxOpenLaunchWeapp;
|
||||
Reference in New Issue
Block a user