// index.tsx
import React, { useState, useEffect, useCallback, FC } from 'react' import { useShareAppMessage, getImageInfo, createCanvasContext, canvasToTempFilePath, getStorageSync, showToast, showModal, hideLoading, showLoading, } from '@tarojs/taro' import { AtActionSheet } from 'taro-ui' import { View, Image, Button, Canvas } from '@tarojs/components' import MainLayout from '@/layout/MainLayout' import LoginButton from '@/components/LoginButton' import ShareImageModal from './ShareImageModal' import { fetchQRCode } from '@/apis/detail' import shareImage from './share-image-icon.png' import shareWx from './share-wx-icon.png' import sharePaper from './sharePaper.png' import activeDetail from './activeDetail@3x.png' import './index.scss' const Spread: FC = () => { // const userInfo = useUserInfo<IRecommenderUserInfo>() const [isOpen, setIsOpen] = useState(false) const [shareImageModal, setShareImageModal] = useState(false) const [isLoading, setIsLoading] = useState(false) const [qrCodeUrl, setQrCodeUrl] = useState<string>('') const [tpmPath, setTpmPath] = useState('') const [loadFlag, setLoadFlag] = useState(false) const [imgHeight, setImgHeight] = useState<number>(1) const isAccredit = getStorageSync('accredit') || false const onShareClick = val => { if (val) { // setIsOpen(true) setShareImageModal(true) } else { showToast({ title: '请授权手机号!', icon: 'none' }) } } useShareAppMessage(() => ({ title: '1234', path: ``, imageUrl: '', })) const onShareWX = () => { setTimeout(() => setIsOpen(false), 250) } const onShareImg = () => { setShareImageModal(true) setTimeout(() => setIsOpen(false), 250) } useEffect(() => { setIsLoading(true) fetchQRCode() .then(res => setQrCodeUrl(res.qrCodeUrl)) .finally(() => setIsLoading(false)) }, []) const imgToCanvas = useCallback(async () => { if (qrCodeUrl) { const canvas = createCanvasContext('canvasId') // showLoading({ // title: '正在保存', // mask: true // }) getImageInfo({ src: sharePaper, }).then((res: any) => { canvas.drawImage(sharePaper, 0, 0, res.width / 3, res.height / 3) getImageInfo({ src: qrCodeUrl, }) .then(qrRes => { canvas.drawImage( qrRes.path, // 将网络图片路径转成本地路径,因为drawImage的第一个参数是图片的本地路径;(所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)) 30, res.height / 3 - 115, qrRes.width / 5, qrRes.height / 5 ) canvas.draw(true, () => { canvasToTempFilePath({ canvasId: 'canvasId', fileType: 'png', }).then(res => setTpmPath(res.tempFilePath)) }) }) .catch(res => { // hideLoading() showModal({ title: '温馨提示', content: '小程序码图片合成失败,请重试', showCancel: false, }) }) }) } }, [qrCodeUrl]) useEffect(() => { imgToCanvas() }, [imgToCanvas]) const onl oadImg = res => { setLoadFlag(true) setImgHeight(res.detail.height / 2) } const renderMenu = () => { return ( <AtActionSheet isOpened={isOpen} cancelText="取消" onCancel={() => setIsOpen(false)}> <Button hoverClass="hover" onClick={onShareWX} openType="share"> <Image src={shareWx} className="img" /> <View className="txt">分享微信</View> </Button> <View onClick={onShareImg}> <Image src={shareImage} className="img" /> <View className="txt">分享图片</View> </View> </AtActionSheet> ) } return ( <MainLayout className="spread"> <View> <Image className="wp100" style={{ height: loadFlag ? imgHeight : 1 }} src={activeDetail} onl oad={onLoadImg} // mode="widthFix" /> {/* <Image className="wp100" src={activeDetail} mode="widthFix" /> */} {/* {renderMenu()} */} <Canvas canvasId="canvasId" className="canvas"></Canvas> <View className="botm"> {isAccredit ? ( <Button className="btn" onClick={onShareClick}> 分享返利 </Button> ) : ( <LoginButton className="btn" onLoginSuccess={onShareClick}> 分享返利 </LoginButton> )} </View> <ShareImageModal isOpen={shareImageModal} onClose={() => setShareImageModal(false)} tpmPath={tpmPath} /> </View> </MainLayout> ) } export default Spread 其中canvas的样式,为了不在当当前页面展示 .canvas{ width: 375PX; height: 812PX; visibility: hidden; position: fixed; top: 99999px; left: 99999px; }
// ShareImageModal.tsx
import React, { useEffect, useState } from 'react' import c from 'classnames' import { noop } from 'lodash' import { showLoading, hideLoading, showToast, authorize, saveImageToPhotosAlbum, // getFileSystemManager } from '@tarojs/taro' import { View, Image, Swiper, SwiperItem } from '@tarojs/components' import { useDebounce } from 'ahooks' import './shareimage.scss' export interface IShareImageModalProps extends IProps, IModalProps { title?: string onClose?(): void sharePath?: string tpmPath: string } const ShareImageModal: React.FC<IShareImageModalProps> = props => { const { isOpen, onClose = noop, sharePath = '', tpmPath} = props const debouncedOpen = useDebounce(isOpen, { wait: 250 }) const delayOpen = useDebounce(isOpen, { wait: 10 }) && isOpen const [current, setCurrent] = useState(1) const [images, setImages] = useState<Array<{ fileUrl: string; enabled: boolean; id: number }>>([]) useEffect(() => { if (isOpen) { Promise.resolve([{fileUrl: tpmPath, enabled:false, id:1 }]) .then(res => { setImages(res) setCurrent(Math.max(0, Math.floor((res.length - 1) / 2))) }) } }, [sharePath, isOpen, images.length]) // 点击保存图片 const saveImageClickHandler = () => { showLoading({ title: '生成分享图...', mask: true }) authorize({ scope: 'scope.writePhotosAlbum' }) .then(() => { saveImageToPhotosAlbum({ filePath: tpmPath, success (res: TaroGeneral.CallbackResult) { showToast({ title: '分享图已成功保存到相册', icon: 'none' }) }, fail (res: TaroGeneral.CallbackResult) { showToast({ title: '生成分享图失败,请重试', icon: 'none' }) }, complete (res: TaroGeneral.CallbackResult) { hideLoading() } }) }) .catch(() => void showToast({ title: '请授权保存图片权限以保存分享图', icon: 'none' })) } // 图片选择器切换 const swiperChangeHandler = e => { const newIndex = e.detail.current setCurrent(newIndex) } // 此时弹窗处于关闭状态 if (!isOpen && !debouncedOpen) { return null } return ( <View className="share-image-modal" catchMove> <View className={c('share-image-modal__body', { 'share-image-modal__body--actived': delayOpen, })} > <View className="menu-content"> <Swiper current={current} onChange={swiperChangeHandler} previousMargin="140rpx" nextMargin="140rpx" duration={200} className="swiper" > {images.map((item, index) => ( <SwiperItem onClick={() => void setCurrent(index)} className="swiper__item" key={item.id} > <Image className={c('swiper__image', { current: index === current })} src={item.fileUrl} mode="aspectFit" /> </SwiperItem> ))} </Swiper> </View> <View onClick={onClose} className="close at-icon at-icon-close"></View> <View onClick={saveImageClickHandler} className="confirm"> 保存图片 </View> </View> <View className={c('share-image-modal__mask', { 'share-image-modal__mask--actived': delayOpen, })} ></View> </View> ) } export default ShareImageModal
// shareimage.scss
.share-image-modal { z-index: 5; &__mask { position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.6); transition: opacity 250ms; opacity: 0; &--actived { opacity: 1; } } &__body { z-index: 8; position: fixed; width: 100%; overflow: hidden; transition: all 250ms; bottom: calc(-1100px - #{$pageBottom}); &--actived { bottom: 0; } .menu-content { background: #F5F6FA; border-radius: 24px 24px 0px 0px; height: 1000px; padding: 70px 0 60px; } .swiper-skeleton { height: 866px; width: 100%; background: transparent; } .swiper { height: 866px; width: 100%; &__item { width: 400px; height: 866px; display: flex; align-items: center; justify-content: center; } &__image { width: 360px; height: 780px; box-shadow: 0px 4px 28px 0px rgba(0, 0, 0, 0.15); border-radius: 16px; transition: all 200ms; &.current { width: 400px; height: 866px; } } } .confirm { background: #FFFFFF; bottom: 0; height: calc(100px + #{$pageBottom}); padding-bottom: $pageBottom; line-height: 80px; text-align: center; font-size: 32px; font-weight: 500; color: #0F1E3E; line-height: 100px; } .close { position: absolute; top: 28px; right: 28px; font-size: 30px; color: #C2C2C2; &::after { content: ' '; z-index: 15; position: absolute; top: -30px; bottom: -30px; right: -30px; left: -30px; } } } }