微信分享功能
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { Button, Text, View } from '@tarojs/components';
|
||||||
|
import { handleShareButtonClick, type PageSharePayload } from '../lib/wechat-share';
|
||||||
|
|
||||||
|
type ShareNavButtonProps = {
|
||||||
|
payload?: PageSharePayload;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶栏分享:
|
||||||
|
* - 小程序:open-type=share 弹出微信分享面板
|
||||||
|
* - H5 微信:JSSDK 配置后提示点右上角 ···
|
||||||
|
*/
|
||||||
|
export default function ShareNavButton({ payload }: ShareNavButtonProps) {
|
||||||
|
if (process.env.TARO_ENV === 'weapp') {
|
||||||
|
return (
|
||||||
|
<Button className="page-nav-bar__btn page-nav-bar__share-btn" openType="share" hoverClass="none">
|
||||||
|
<Text className="page-nav-bar__icon page-nav-bar__icon--share">⤴</Text>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className="page-nav-bar__btn"
|
||||||
|
onClick={() => {
|
||||||
|
void handleShareButtonClick(payload);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text className="page-nav-bar__icon page-nav-bar__icon--share">⤴</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import Taro, { useDidShow } from '@tarojs/taro';
|
||||||
|
import { captureIosJssdkEntryUrl } from '@dukang/weixin-sdk';
|
||||||
|
import { applyWechatShare, type PageSharePayload } from '../lib/wechat-share';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H5:进入页面时刷新微信分享卡片;
|
||||||
|
* 小程序:开启右上角分享菜单。
|
||||||
|
*/
|
||||||
|
export default function WechatShareReady({ payload }: { payload?: PageSharePayload }) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (process.env.TARO_ENV === 'h5') {
|
||||||
|
captureIosJssdkEntryUrl();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
if (process.env.TARO_ENV === 'weapp') {
|
||||||
|
void Taro.showShareMenu({
|
||||||
|
withShareTicket: true,
|
||||||
|
showShareItems: ['shareAppMessage', 'shareTimeline'],
|
||||||
|
}).catch(() => {
|
||||||
|
void Taro.showShareMenu({ withShareTicket: true }).catch(() => {});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void applyWechatShare({
|
||||||
|
title: payload?.title,
|
||||||
|
desc: payload?.desc,
|
||||||
|
imgUrl: payload?.imgUrl,
|
||||||
|
link: payload?.link,
|
||||||
|
}).catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (process.env.TARO_ENV !== 'h5') return;
|
||||||
|
void applyWechatShare({
|
||||||
|
title: payload?.title,
|
||||||
|
desc: payload?.desc,
|
||||||
|
imgUrl: payload?.imgUrl,
|
||||||
|
link: payload?.link,
|
||||||
|
}).catch(() => {});
|
||||||
|
}, [payload?.title, payload?.desc, payload?.imgUrl, payload?.link]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import { BRAND_LOGO_URL } from '@dukang/shared-types';
|
||||||
|
import type { WechatShareData } from '@dukang/weixin-sdk';
|
||||||
|
import { getWechatShareLink, isWechatBrowser, isMiniProgram } from '@dukang/weixin-sdk';
|
||||||
|
import { toast } from './api';
|
||||||
|
import { isWechatEnv, weixinSdk } from './weixin';
|
||||||
|
|
||||||
|
export const DEFAULT_SHARE_TITLE = '你吃饭,杜康买单';
|
||||||
|
export const DEFAULT_SHARE_DESC = '杜康好客 · 买酒享权益,全城门店可用';
|
||||||
|
export const WECHAT_SHARE_HINT = '请点击右上角 ··· 分享给好友';
|
||||||
|
|
||||||
|
export function getDefaultShareImageUrl(): string {
|
||||||
|
return BRAND_LOGO_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDefaultShareData(
|
||||||
|
overrides?: Partial<WechatShareData>,
|
||||||
|
): WechatShareData {
|
||||||
|
let link = overrides?.link;
|
||||||
|
if (!link) {
|
||||||
|
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') {
|
||||||
|
link = getWechatShareLink();
|
||||||
|
} else {
|
||||||
|
link = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: overrides?.title ?? DEFAULT_SHARE_TITLE,
|
||||||
|
desc: overrides?.desc ?? DEFAULT_SHARE_DESC,
|
||||||
|
link,
|
||||||
|
imgUrl: overrides?.imgUrl ?? getDefaultShareImageUrl(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 配置 H5 微信内右上角分享卡片 */
|
||||||
|
export async function applyWechatShare(
|
||||||
|
overrides?: Partial<WechatShareData>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (process.env.TARO_ENV !== 'h5') return;
|
||||||
|
if (!isWechatBrowser()) return;
|
||||||
|
await weixinSdk.setShare(buildDefaultShareData(overrides));
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageSharePayload = {
|
||||||
|
title?: string;
|
||||||
|
desc?: string;
|
||||||
|
/** 小程序分享 path,如 /pages/product-detail/index?id=1 */
|
||||||
|
path?: string;
|
||||||
|
imgUrl?: string;
|
||||||
|
/** H5 自定义分享 link,默认当前页 */
|
||||||
|
link?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 点击「分享」按钮 */
|
||||||
|
export async function handleShareButtonClick(payload?: PageSharePayload): Promise<void> {
|
||||||
|
// 小程序:由 Button open-type="share" 触发,无需在此拉起
|
||||||
|
if (process.env.TARO_ENV === 'weapp' || isMiniProgram()) {
|
||||||
|
try {
|
||||||
|
await Taro.showShareMenu({ withShareTicket: true, showShareItems: ['shareAppMessage', 'shareTimeline'] });
|
||||||
|
} catch {
|
||||||
|
/* 旧基础库可能不支持 showShareItems */
|
||||||
|
try {
|
||||||
|
await Taro.showShareMenu({ withShareTicket: true });
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toast(WECHAT_SHARE_HINT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWechatEnv()) {
|
||||||
|
toast('请在微信内打开后分享');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await applyWechatShare({
|
||||||
|
title: payload?.title,
|
||||||
|
desc: payload?.desc,
|
||||||
|
imgUrl: payload?.imgUrl,
|
||||||
|
link: payload?.link,
|
||||||
|
});
|
||||||
|
toast(WECHAT_SHARE_HINT);
|
||||||
|
} catch {
|
||||||
|
toast('分享配置失败,请刷新后重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 供 useShareAppMessage 使用的标题/路径/图 */
|
||||||
|
export function toWeappShareMessage(payload?: PageSharePayload) {
|
||||||
|
return {
|
||||||
|
title: payload?.title || DEFAULT_SHARE_TITLE,
|
||||||
|
path: payload?.path || '/pages/home/index',
|
||||||
|
imageUrl: payload?.imgUrl || getDefaultShareImageUrl(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ClientApp } from '@dukang/shared-types';
|
||||||
|
import { createWeixinSdk, isWechatEnv } from '@dukang/weixin-sdk';
|
||||||
|
import { API_BASE, getToken } from './api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H5 公众号内 JSSDK(分享 / 定位等)走 USER_H5 对应的公众号 appId。
|
||||||
|
* 小程序原生能力不经此门面。
|
||||||
|
*/
|
||||||
|
export const weixinSdk = createWeixinSdk({
|
||||||
|
apiBase: API_BASE,
|
||||||
|
clientApp: process.env.TARO_ENV === 'h5' ? ClientApp.USER_H5 : ClientApp.USER_MINI,
|
||||||
|
getAccessToken: () => getToken() || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { isWechatEnv };
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
navigationBarTitleText: '商品详情',
|
navigationBarTitleText: '商品详情',
|
||||||
|
enableShareAppMessage: true,
|
||||||
|
enableShareTimeline: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text, Image } from '@tarojs/components';
|
import { View, Text, Image } from '@tarojs/components';
|
||||||
import Taro, { usePageScroll, useRouter } from '@tarojs/taro';
|
import Taro, { usePageScroll, useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
||||||
import type { ProductDetailContentDto } from '@dukang/shared-types';
|
import type { ProductDetailContentDto } from '@dukang/shared-types';
|
||||||
import PageShell from '../../components/PageShell';
|
import PageShell from '../../components/PageShell';
|
||||||
import PageNavBar from '../../components/PageNavBar';
|
import PageNavBar from '../../components/PageNavBar';
|
||||||
import ProductCarousel from '../../components/ProductCarousel';
|
import ProductCarousel from '../../components/ProductCarousel';
|
||||||
|
import ShareNavButton from '../../components/ShareNavButton';
|
||||||
|
import WechatShareReady from '../../components/WechatShareReady';
|
||||||
import { goLogin } from '../../lib/auth-nav';
|
import { goLogin } from '../../lib/auth-nav';
|
||||||
import { ensurePayReady } from '../../lib/pay-ready';
|
import { ensurePayReady } from '../../lib/pay-ready';
|
||||||
import { isLoggedIn, request, toast } from '../../lib/api';
|
import { isLoggedIn, request, toast } from '../../lib/api';
|
||||||
import {
|
import {
|
||||||
getProductCarouselImages,
|
getProductCarouselImages,
|
||||||
getProductDetailImages,
|
getProductDetailImages,
|
||||||
|
getProductMainImage,
|
||||||
type ProductImageSource,
|
type ProductImageSource,
|
||||||
} from '../../lib/product-images';
|
} from '../../lib/product-images';
|
||||||
|
import {
|
||||||
|
DEFAULT_SHARE_DESC,
|
||||||
|
DEFAULT_SHARE_TITLE,
|
||||||
|
toWeappShareMessage,
|
||||||
|
} from '../../lib/wechat-share';
|
||||||
import iconHome from '../../assets/tabbar/home.png';
|
import iconHome from '../../assets/tabbar/home.png';
|
||||||
|
|
||||||
type Product = ProductImageSource & {
|
type Product = ProductImageSource & {
|
||||||
@@ -42,6 +50,23 @@ export default function ProductDetailPage() {
|
|||||||
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
||||||
}, [productId]);
|
}, [productId]);
|
||||||
|
|
||||||
|
const sharePayload = useMemo(
|
||||||
|
() => ({
|
||||||
|
title: product?.name || DEFAULT_SHARE_TITLE,
|
||||||
|
desc: product?.subtitle || DEFAULT_SHARE_DESC,
|
||||||
|
path: `/pages/product-detail/index?id=${productId}`,
|
||||||
|
imgUrl: (product ? getProductMainImage(product) : '') || undefined,
|
||||||
|
}),
|
||||||
|
[product, productId],
|
||||||
|
);
|
||||||
|
|
||||||
|
useShareAppMessage(() => toWeappShareMessage(sharePayload));
|
||||||
|
useShareTimeline(() => ({
|
||||||
|
title: sharePayload.title || DEFAULT_SHARE_TITLE,
|
||||||
|
query: productId ? `id=${productId}` : '',
|
||||||
|
imageUrl: sharePayload.imgUrl,
|
||||||
|
}));
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
const pages = Taro.getCurrentPages();
|
const pages = Taro.getCurrentPages();
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
@@ -84,16 +109,13 @@ export default function ProductDetailPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell variant="scroll" className="product-detail-page" hasFixedFooter>
|
<PageShell variant="scroll" className="product-detail-page" hasFixedFooter>
|
||||||
|
<WechatShareReady payload={sharePayload} />
|
||||||
<PageNavBar
|
<PageNavBar
|
||||||
title={product.name}
|
title={product.name}
|
||||||
solid={headerSolid}
|
solid={headerSolid}
|
||||||
titleVisible={headerSolid}
|
titleVisible={headerSolid}
|
||||||
onBack={goBack}
|
onBack={goBack}
|
||||||
right={(
|
right={<ShareNavButton payload={sharePayload} />}
|
||||||
<View className="page-nav-bar__btn" onClick={() => toast('分享功能开发中')}>
|
|
||||||
<Text className="page-nav-bar__icon page-nav-bar__icon--share">⤴</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View className="product-detail-main">
|
<View className="product-detail-main">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
navigationBarTitleText: '门店详情',
|
navigationBarTitleText: '门店详情',
|
||||||
|
enableShareAppMessage: true,
|
||||||
|
enableShareTimeline: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text } from '@tarojs/components';
|
import { View, Text } from '@tarojs/components';
|
||||||
import Taro, { usePageScroll, useRouter } from '@tarojs/taro';
|
import Taro, { usePageScroll, useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
||||||
import PageShell from '../../components/PageShell';
|
import PageShell from '../../components/PageShell';
|
||||||
import PageNavBar from '../../components/PageNavBar';
|
import PageNavBar from '../../components/PageNavBar';
|
||||||
import ProductCarousel from '../../components/ProductCarousel';
|
import ProductCarousel from '../../components/ProductCarousel';
|
||||||
|
import ShareNavButton from '../../components/ShareNavButton';
|
||||||
|
import WechatShareReady from '../../components/WechatShareReady';
|
||||||
import { request, toast } from '../../lib/api';
|
import { request, toast } from '../../lib/api';
|
||||||
|
import {
|
||||||
|
DEFAULT_SHARE_DESC,
|
||||||
|
DEFAULT_SHARE_TITLE,
|
||||||
|
toWeappShareMessage,
|
||||||
|
} from '../../lib/wechat-share';
|
||||||
|
|
||||||
type Store = {
|
type Store = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -44,6 +51,23 @@ export default function StoreDetailPage() {
|
|||||||
});
|
});
|
||||||
}, [storeId]);
|
}, [storeId]);
|
||||||
|
|
||||||
|
const sharePayload = useMemo(
|
||||||
|
() => ({
|
||||||
|
title: store?.name || DEFAULT_SHARE_TITLE,
|
||||||
|
desc: store?.address || DEFAULT_SHARE_DESC,
|
||||||
|
path: `/pages/store-detail/index?id=${storeId}`,
|
||||||
|
imgUrl: store?.coverUrl || store?.carouselUrls?.[0] || undefined,
|
||||||
|
}),
|
||||||
|
[store, storeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
useShareAppMessage(() => toWeappShareMessage(sharePayload));
|
||||||
|
useShareTimeline(() => ({
|
||||||
|
title: sharePayload.title || DEFAULT_SHARE_TITLE,
|
||||||
|
query: storeId ? `id=${storeId}` : '',
|
||||||
|
imageUrl: sharePayload.imgUrl,
|
||||||
|
}));
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
const pages = Taro.getCurrentPages();
|
const pages = Taro.getCurrentPages();
|
||||||
if (pages.length > 1) Taro.navigateBack();
|
if (pages.length > 1) Taro.navigateBack();
|
||||||
@@ -68,11 +92,13 @@ export default function StoreDetailPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell variant="scroll" className="store-detail-page" hasFixedFooter>
|
<PageShell variant="scroll" className="store-detail-page" hasFixedFooter>
|
||||||
|
<WechatShareReady payload={sharePayload} />
|
||||||
<PageNavBar
|
<PageNavBar
|
||||||
title={store.name}
|
title={store.name}
|
||||||
solid={headerSolid}
|
solid={headerSolid}
|
||||||
titleVisible={headerSolid}
|
titleVisible={headerSolid}
|
||||||
onBack={goBack}
|
onBack={goBack}
|
||||||
|
right={<ShareNavButton payload={sharePayload} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View className="store-detail-hero full-bleed">
|
<View className="store-detail-hero full-bleed">
|
||||||
@@ -98,17 +124,9 @@ export default function StoreDetailPage() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="store-detail-section">
|
|
||||||
<Text className="store-detail-section-title">门店服务</Text>
|
|
||||||
<Text className="store-detail-meta">支持好客权益到店核销,部分门店提供包间预约。</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="store-detail-bar">
|
<View className="store-detail-bar">
|
||||||
<View
|
<View className="u-btn u-btn--block" onClick={() => toast('核销请前往「好客权益」')}>
|
||||||
className="store-detail-bar-btn store-detail-bar-btn--primary store-detail-bar-btn--full"
|
<Text>到店核销</Text>
|
||||||
onClick={() => Taro.navigateTo({ url: '/pages/redeem/index' })}
|
|
||||||
>
|
|
||||||
<Text>去核销</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</PageShell>
|
</PageShell>
|
||||||
|
|||||||
@@ -169,6 +169,24 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 小程序分享 Button 去默认样式,对齐圆形胶囊按钮 */
|
||||||
|
.page-nav-bar__share-btn {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
background: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-nav-bar__share-btn::after {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-nav-bar--solid .page-nav-bar__share-btn {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.page-nav-bar__title {
|
.page-nav-bar__title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user