233 lines
6.7 KiB
JavaScript
233 lines
6.7 KiB
JavaScript
// src/hooks/useWechatShare.js
|
||
// 微信分享 Hook - 支持自动配置右上角分享菜单和显式分享引导
|
||
|
||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||
import { getJsSdkConfig } from '@services/miniprogramService';
|
||
|
||
/**
|
||
* 检测是否在微信浏览器中
|
||
*/
|
||
const isWechatBrowser = () => {
|
||
const ua = navigator.userAgent.toLowerCase();
|
||
return ua.includes('micromessenger');
|
||
};
|
||
|
||
/**
|
||
* 微信分享 Hook
|
||
*
|
||
* @param {Object} options - 分享配置
|
||
* @param {string} options.title - 分享标题
|
||
* @param {string} options.desc - 分享描述
|
||
* @param {string} options.link - 分享链接(默认当前页面)
|
||
* @param {string} options.imgUrl - 分享图标 URL
|
||
* @param {boolean} options.autoSetup - 是否自动配置分享(默认 true)
|
||
*
|
||
* @returns {Object} 分享状态和方法
|
||
*/
|
||
const useWechatShare = (options = {}) => {
|
||
const {
|
||
title = '',
|
||
desc = '',
|
||
link = '',
|
||
imgUrl = '',
|
||
autoSetup = true,
|
||
} = options;
|
||
|
||
const [isReady, setIsReady] = useState(false);
|
||
const [isInWechat, setIsInWechat] = useState(false);
|
||
const [error, setError] = useState(null);
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
|
||
// 使用 ref 存储最新的分享配置,避免重复初始化
|
||
const shareConfigRef = useRef({ title, desc, link, imgUrl });
|
||
const isInitializedRef = useRef(false);
|
||
|
||
// 更新 ref 中的配置
|
||
useEffect(() => {
|
||
shareConfigRef.current = { title, desc, link, imgUrl };
|
||
|
||
// 如果已初始化且配置变化,更新分享内容
|
||
if (isReady && isInWechat && window.wx) {
|
||
updateShareData();
|
||
}
|
||
}, [title, desc, link, imgUrl, isReady, isInWechat]);
|
||
|
||
/**
|
||
* 更新分享数据
|
||
*/
|
||
const updateShareData = useCallback(() => {
|
||
if (!window.wx || !isReady) return;
|
||
|
||
const config = shareConfigRef.current;
|
||
const shareLink = config.link || window.location.href.split('#')[0];
|
||
|
||
// 分享给朋友
|
||
window.wx.updateAppMessageShareData({
|
||
title: config.title || document.title,
|
||
desc: config.desc || '',
|
||
link: shareLink,
|
||
imgUrl: config.imgUrl || `${window.location.origin}/logo192.png`,
|
||
success: () => {
|
||
console.log('[WechatShare] updateAppMessageShareData 设置成功');
|
||
},
|
||
fail: (err) => {
|
||
console.error('[WechatShare] updateAppMessageShareData 失败:', err);
|
||
}
|
||
});
|
||
|
||
// 分享到朋友圈
|
||
window.wx.updateTimelineShareData({
|
||
title: config.title || document.title,
|
||
link: shareLink,
|
||
imgUrl: config.imgUrl || `${window.location.origin}/logo192.png`,
|
||
success: () => {
|
||
console.log('[WechatShare] updateTimelineShareData 设置成功');
|
||
},
|
||
fail: (err) => {
|
||
console.error('[WechatShare] updateTimelineShareData 失败:', err);
|
||
}
|
||
});
|
||
}, [isReady]);
|
||
|
||
/**
|
||
* 初始化微信 JS-SDK
|
||
*/
|
||
const initWxSdk = useCallback(async () => {
|
||
if (isInitializedRef.current || !isInWechat) return;
|
||
|
||
setIsLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
// 获取当前页面 URL(不含 hash)
|
||
const currentUrl = window.location.href.split('#')[0];
|
||
|
||
// 获取 JS-SDK 签名配置
|
||
const config = await getJsSdkConfig(currentUrl);
|
||
|
||
if (!config) {
|
||
throw new Error('获取签名配置失败');
|
||
}
|
||
|
||
// 配置微信 JS-SDK
|
||
window.wx.config({
|
||
debug: false, // 生产环境关闭
|
||
appId: config.appId,
|
||
timestamp: config.timestamp,
|
||
nonceStr: config.nonceStr,
|
||
signature: config.signature,
|
||
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'],
|
||
});
|
||
|
||
// 监听 ready 事件
|
||
window.wx.ready(() => {
|
||
console.log('[WechatShare] wx.ready 触发');
|
||
isInitializedRef.current = true;
|
||
setIsReady(true);
|
||
setIsLoading(false);
|
||
|
||
// 自动设置分享内容
|
||
if (autoSetup) {
|
||
updateShareData();
|
||
}
|
||
});
|
||
|
||
// 监听 error 事件
|
||
window.wx.error((err) => {
|
||
console.error('[WechatShare] wx.error:', err);
|
||
setError(err.errMsg || '微信 JS-SDK 初始化失败');
|
||
setIsLoading(false);
|
||
});
|
||
|
||
} catch (err) {
|
||
console.error('[WechatShare] 初始化失败:', err);
|
||
setError(err.message || '初始化失败');
|
||
setIsLoading(false);
|
||
}
|
||
}, [isInWechat, autoSetup, updateShareData]);
|
||
|
||
// 检测微信环境并初始化
|
||
useEffect(() => {
|
||
const inWechat = isWechatBrowser();
|
||
setIsInWechat(inWechat);
|
||
|
||
if (inWechat && autoSetup) {
|
||
// 确保 wx 对象已加载
|
||
if (window.wx) {
|
||
initWxSdk();
|
||
} else {
|
||
// 等待 wx 对象加载
|
||
const checkWx = setInterval(() => {
|
||
if (window.wx) {
|
||
clearInterval(checkWx);
|
||
initWxSdk();
|
||
}
|
||
}, 100);
|
||
|
||
// 3秒后停止检查
|
||
setTimeout(() => clearInterval(checkWx), 3000);
|
||
}
|
||
}
|
||
}, [autoSetup, initWxSdk]);
|
||
|
||
/**
|
||
* 手动触发分享引导
|
||
* 在微信中会提示用户点击右上角分享
|
||
* 在非微信环境中可以展示其他分享方式
|
||
*/
|
||
const triggerShare = useCallback(() => {
|
||
if (!isInWechat) {
|
||
// 非微信环境,可以使用 Web Share API 或复制链接
|
||
if (navigator.share) {
|
||
navigator.share({
|
||
title: shareConfigRef.current.title || document.title,
|
||
text: shareConfigRef.current.desc,
|
||
url: shareConfigRef.current.link || window.location.href,
|
||
}).catch((err) => {
|
||
console.log('[WechatShare] Web Share 取消或失败:', err);
|
||
});
|
||
} else {
|
||
// 复制链接到剪贴板
|
||
const link = shareConfigRef.current.link || window.location.href;
|
||
navigator.clipboard?.writeText(link).then(() => {
|
||
console.log('[WechatShare] 链接已复制');
|
||
});
|
||
}
|
||
return { type: 'web', success: true };
|
||
}
|
||
|
||
// 微信环境:无法直接触发分享,返回提示信息
|
||
return {
|
||
type: 'wechat',
|
||
success: true,
|
||
message: '请点击右上角「...」进行分享',
|
||
};
|
||
}, [isInWechat]);
|
||
|
||
/**
|
||
* 手动更新分享配置
|
||
*/
|
||
const setShareConfig = useCallback((newConfig) => {
|
||
shareConfigRef.current = {
|
||
...shareConfigRef.current,
|
||
...newConfig,
|
||
};
|
||
|
||
if (isReady && isInWechat) {
|
||
updateShareData();
|
||
}
|
||
}, [isReady, isInWechat, updateShareData]);
|
||
|
||
return {
|
||
isReady, // JS-SDK 是否就绪
|
||
isInWechat, // 是否在微信浏览器中
|
||
isLoading, // 是否正在加载
|
||
error, // 错误信息
|
||
triggerShare, // 触发分享引导
|
||
setShareConfig, // 手动更新分享配置
|
||
updateShareData, // 手动更新分享数据到微信
|
||
};
|
||
};
|
||
|
||
export default useWechatShare;
|