From 78ac461ef99ba81c79c7555d090c0ef7df666ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Thu, 26 Feb 2026 12:11:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(share):=20=E6=B7=BB=E5=8A=A0=E5=88=86?= =?UTF-8?q?=E4=BA=AB=E5=8A=9F=E8=83=BD=E5=B9=B6=E9=99=90=E5=88=B6=E6=B0=B4?= =?UTF-8?q?=E7=A5=A8=E5=95=86=E5=93=81=E5=8A=A0=E5=85=A5=E8=B4=AD=E7=89=A9?= =?UTF-8?q?=E8=BD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在二维码页面启用分享给朋友和分享到朋友圈功能 - 实现分享菜单显示和分享内容自定义逻辑 - 移除原有的复制邀请信息和分享给好友按钮 - 新增水票套票模板查询接口和类型定义 - 阻止水票套票商品加入购物车并提示用户立即购买 - 添加组件卸载时的清理逻辑防止内存泄漏 - 优化商品详情页异步操作的状态管理 --- src/dealer/qrcode/index.config.ts | 5 ++- src/dealer/qrcode/index.tsx | 59 ++++++++++++++++++------------ src/shop/goodsDetail/index.tsx | 61 ++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/src/dealer/qrcode/index.config.ts b/src/dealer/qrcode/index.config.ts index 18e1216..8c86bc6 100644 --- a/src/dealer/qrcode/index.config.ts +++ b/src/dealer/qrcode/index.config.ts @@ -1,3 +1,6 @@ export default definePageConfig({ - navigationBarTitleText: '桂乐淘分享中心' + navigationBarTitleText: '桂乐淘分享中心', + // Enable "Share to friends" and "Share to Moments" (timeline) for this page. + enableShareAppMessage: true, + enableShareTimeline: true }) diff --git a/src/dealer/qrcode/index.tsx b/src/dealer/qrcode/index.tsx index 3c4aaa0..a721389 100644 --- a/src/dealer/qrcode/index.tsx +++ b/src/dealer/qrcode/index.tsx @@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react' import {View, Text, Image} from '@tarojs/components' import {Button, Loading} from '@nutui/nutui-react-taro' import {Download, QrCode} from '@nutui/icons-react-taro' -import Taro from '@tarojs/taro' +import Taro, {useShareAppMessage} from '@tarojs/taro' import {useDealerUser} from '@/hooks/useDealerUser' import {generateInviteCode} from '@/api/invite' // import type {InviteStats} from '@/api/invite' @@ -16,6 +16,39 @@ const DealerQrcode: React.FC = () => { // const [statsLoading, setStatsLoading] = useState(false) const {dealerUser, loading: dealerLoading, error, refresh} = useDealerUser() + // Enable "转发给朋友" + "分享到朋友圈" items in the share panel/menu. + useEffect(() => { + // Some clients require explicit call to show both share entries. + Taro.showShareMenu({ + withShareTicket: true, + showShareItems: ['shareAppMessage', 'shareTimeline'] + }).catch(() => {}) + }, []) + + // 转发给朋友(分享小程序链接) + useShareAppMessage(() => { + const inviterRaw = dealerUser?.userId ?? Taro.getStorageSync('UserId') + const inviter = Number(inviterRaw) + const hasInviter = Number.isFinite(inviter) && inviter > 0 + + const user = Taro.getStorageSync('User') || {} + const nickname = (user && (user.nickname || user.realName || user.username)) || '' + const title = hasInviter ? `${nickname || '我'}邀请你加入桂乐淘伙伴计划` : '桂乐淘伙伴计划' + + return { + title, + path: hasInviter + ? `/pages/index/index?inviter=${inviter}&source=dealer_qrcode&t=${Date.now()}` + : `/pages/index/index`, + success: function () { + Taro.showToast({title: '分享成功', icon: 'success', duration: 2000}) + }, + fail: function () { + Taro.showToast({title: '分享失败', icon: 'none', duration: 2000}) + } + } + }) + // 生成小程序码 const generateMiniProgramCode = async () => { if (!dealerUser?.userId) { @@ -376,29 +409,7 @@ const DealerQrcode: React.FC = () => { 保存小程序码到相册 - {/**/} - {/* }*/} - {/* onClick={copyInviteInfo}*/} - {/* disabled={!dealerUser?.userId || codeLoading}*/} - {/* >*/} - {/* 复制邀请信息*/} - {/* */} - {/**/} - {/**/} - {/* }*/} - {/* onClick={shareMiniProgramCode}*/} - {/* disabled={!dealerUser?.userId || codeLoading}*/} - {/* >*/} - {/* 分享给好友*/} - {/* */} - {/**/} + {/* 推广说明 */} diff --git a/src/shop/goodsDetail/index.tsx b/src/shop/goodsDetail/index.tsx index 80c04d4..4c4fa19 100644 --- a/src/shop/goodsDetail/index.tsx +++ b/src/shop/goodsDetail/index.tsx @@ -17,6 +17,8 @@ import {useCart} from "@/hooks/useCart"; import {useConfig} from "@/hooks/useConfig"; import {parseInviteParams, saveInviteParams, trackInviteSource} from "@/utils/invite"; import { ensureLoggedIn } from '@/utils/auth' +import {getGltTicketTemplateByGoodsId} from "@/api/glt/gltTicketTemplate"; +import type {GltTicketTemplate} from "@/api/glt/gltTicketTemplate/model"; const GoodsDetail = () => { const [statusBarHeight, setStatusBarHeight] = useState(44); @@ -32,6 +34,8 @@ const GoodsDetail = () => { title: '', content: '' }) + // 水票套票模板:存在时该商品不允许加入购物车(购物车无法支付此类商品) + const [ticketTemplate, setTicketTemplate] = useState(null) // const [selectedSku, setSelectedSku] = useState(null); const [loading, setLoading] = useState(false); const router = Taro.getCurrentInstance().router; @@ -60,9 +64,25 @@ const GoodsDetail = () => { }, [goodsId]) // 处理加入购物车 - const handleAddToCart = () => { + const handleAddToCart = async () => { if (!goods) return; + // 水票套票商品:不允许加入购物车(购物车无法支付) + // 优先使用已加载的 ticketTemplate;若尚未加载则补一次查询 + let tpl = ticketTemplate + if (!tpl && goods?.goodsId) { + try { + tpl = await getGltTicketTemplateByGoodsId(Number(goods.goodsId)) + setTicketTemplate(tpl) + } catch (_e) { + tpl = null + } + } + if (tpl) { + Taro.showToast({title: '该商品为水票套票商品,请点击“立即购买”下单', icon: 'none'}) + return + } + if (!ensureLoggedIn(`/shop/goodsDetail/index?id=${goods.goodsId}`)) return // 如果有规格,显示规格选择器 @@ -99,11 +119,26 @@ const GoodsDetail = () => { }; // 规格选择确认回调 - const handleSpecConfirm = (sku: ShopGoodsSku, quantity: number, action?: 'cart' | 'buy') => { + const handleSpecConfirm = async (sku: ShopGoodsSku, quantity: number, action?: 'cart' | 'buy') => { // setSelectedSku(sku); setShowSpecSelector(false); if (action === 'cart') { + // 水票套票商品:不允许加入购物车(购物车无法支付) + let tpl = ticketTemplate + if (!tpl && goods?.goodsId) { + try { + tpl = await getGltTicketTemplateByGoodsId(Number(goods.goodsId)) + setTicketTemplate(tpl) + } catch (_e) { + tpl = null + } + } + if (tpl) { + Taro.showToast({title: '该商品为水票套票商品,请点击“立即购买”下单', icon: 'none'}) + return + } + // 加入购物车 addToCart({ goodsId: goods!.goodsId!, @@ -143,14 +178,18 @@ const GoodsDetail = () => { } useEffect(() => { + let alive = true Taro.getSystemInfo({ success: (res) => { + if (!alive) return setWindowWidth(res.windowWidth) setStatusBarHeight(Number(res.statusBarHeight) + 5) }, }); if (goodsId) { setLoading(true); + // 切换商品时先重置套票模板,避免复用上一个商品状态 + setTicketTemplate(null) // 加载商品详情 getShopGoods(Number(goodsId)) @@ -159,6 +198,7 @@ const GoodsDetail = () => { if (res.content) { res.content = wxParse(res.content); } + if (!alive) return setGoods(res); if (res.files) { const arr = JSON.parse(res.files); @@ -169,12 +209,25 @@ const GoodsDetail = () => { console.error("Failed to fetch goods detail:", error); }) .finally(() => { + if (!alive) return setLoading(false); }); + // 查询商品是否绑定水票模板(失败/无数据时不影响正常浏览) + getGltTicketTemplateByGoodsId(Number(goodsId)) + .then((tpl) => { + if (!alive) return + setTicketTemplate(tpl) + }) + .catch((_e) => { + if (!alive) return + setTicketTemplate(null) + }) + // 加载商品规格 listShopGoodsSpec({goodsId: Number(goodsId)} as any) .then((data) => { + if (!alive) return setSpecs(data || []); }) .catch((error) => { @@ -184,12 +237,16 @@ const GoodsDetail = () => { // 加载商品SKU listShopGoodsSku({goodsId: Number(goodsId)} as any) .then((data) => { + if (!alive) return setSkus(data || []); }) .catch((error) => { console.error("Failed to fetch goods skus:", error); }); } + return () => { + alive = false + } }, [goodsId]); // 分享给好友