微信小程序 非webview分享给好友及生成分享海报

微信小程序 非webview分享给好友及分享海报

UI展示

在这里插入图片描述
点击分享显示分享sheet:
在这里插入图片描述
点击生成海报,展示海报预览图片:
在这里插入图片描述

组件目录结构:

在这里插入图片描述
在这里插入图片描述

代码

works文件

woks.json中引入: "share-sheet": "/components/common/share/share-sheet/share-sheet",
works.wxml中引入:<share-sheet show="{{showShareSheet}}" share-data="{{shareData}}" />
works.js中相关:

data: {
   		// ...
        /*分享相关参数 */
        showShareSheet: false, //是否显示分享弹窗
        shareData: null, //默认为null
        // shareData: {
        //     showImg: '', //分享图片
        //     title: '', //标题
        //     subordinateTitle: '',  //次标题
        // designerHeadPortrait: '', //设计师头像
        // 			designerName: '', //设计师名字
        //     qrCodeImg: '', //分享二维码图片
        // }, //获取分享的相关数据
    },
     getWorkDetail(id) {
     	// ...
     	//异步请求数据设置分享数据
     	this.setData({
     		shareData
     	})
     	/ ...
     },
    // 点击分享按钮
    shareAction() {
        this.setData({
            showShareSheet: true,
        });
    },
该处使用的url网络请求的数据。

share-sheet文件

share-sheet组件见https://blog.csdn.net/feiyupang/article/details/125605602

poster-share文件

json文件引入:"painter":"/components/painter/painter"
wxml:

<painter palette="{{posterData}}" use2D="{{true}}"  customStyle='position: absolute; left: -9999rpx;'  bind:imgOK="onImgOK"  bind:imgErr="onImgErr" />
<!-- use2D -->
<view class="preview-poster-container" hidden="{{ !show }}" catchtouchmove="poptouchmove">
    <view class="shade" ></view>
    <view class="content" style="max-height: {{maxHeight}}px">
            <view class="close-bar">
                <view class="close-btn" catchtap="closePreviewPop">
                    <image class="close-icon" src="/icons/cover-view/close-white.png"></image>
                </view>
            </view>
             <scroll-view scroll-y class="poster-box" style="height: {{imgHeight}}px">
                <image wx:if="{{tempFilePath}}" src="{{tempFilePath}}" class="poster" mode="widthFix" bindload="filePathLoaded" binderror="filePathError"></image>
            </scroll-view>
            <view class="save-btn" catchtap="savePoster" >保存图片</view>
    </view>
</view>
<!--获取文字高度使用-->
<canvas type="2d" id="heightCanvas" style="position: absolute; left: -9999rpx;"></canvas>

js:

const app = getApp();
Component({
    options: {
        addGlobalClass: true,
    },
    /**
     * 组件的属性列表
     */
    properties: {
        show: {
            type: Boolean,
            value: false
        },
        shareData: {
            type: Object,
            value: null,
            observer(val) {
                if (val) {
                    this.data.isCreating = true;
                    this.setPosterData();
                }
            },
        },
        // 是否在生成海报之前点击了生成海报按钮
        clickedCreateBtn: {
            type: Boolean,
            value: false,
            observer(val) {
                if (val) {
                    if (!this.data.shareData) {
                        this.data.isCreating = false;
                        this.data.clickedCreateBtn = false;
                        wx.showToast({
                            title: '分享数据不存在',
                            icon: 'none',
                        });
                        return;
                    }
                    wx.showLoading({
                        title: '生成中...',
                        mask: true,
                    });
                    if (!this.data.isCreating) {
                        this.setPosterData();
                    }
                }
            },
        },
    },
    lifetimes: {
        attached: function () {
            this.attachedMethod();
        },
    },
    attached: function () {
        this.attachedMethod();
    },
    /**
     * 组件的初始数据
     */
    data: {
        // 分享相关参数
        posterData: null,
        tempFilePath: '', //生成的海报图片临时存储路径
        imgHeight: 0, // 盛放图片的盒子高度
        maxHeight: 0, // 海报预览的最大高度
        isCreating: false, //是否正在生成中
    },
    /**
     * 组件的方法列表
     */
    methods: {
        attachedMethod() {
            let self = this;
            let pages = getCurrentPages(),
                currentPage = pages[pages.length - 1];
            self.data.currentPage = currentPage;

            app.getSystemInfo(function (systemMsg) {
                // 根据实际情况调整
                const maxHeight = Math.floor(
                    systemMsg.height -
                        systemMsg.navigationBarHeight -
                        (68 * systemMsg.screenWidth) / 750
                );
                /* 海报预览盒子中海报的最大高度 
                     根据实际情况调整
                */
                const maxImgHeight = Math.floor(
                    maxHeight - ((68 + 156) * systemMsg.screenWidth) / 750
                );
                self.data.screenWidth = systemMsg.screenWidth;
                self.data.maxImgHeight = maxImgHeight || 0;

                self.setData({
                    maxHeight,
                });
            });
        },
        //阻止滑动事件 防止page滑动
        poptouchmove: function () {
            return false;
        },
        //获取ctx
        getTextHeightCtx() {
            const self = this;
            return new Promise(resolve => {
                const query = wx.createSelectorQuery().in(self);
                query
                    .select('#heightCanvas')
                    .fields({ node: true, size: true })
                    .exec(res => {
                        const canvasNode = res[0].node;
                        const ctx = canvasNode.getContext('2d');
                        resolve(ctx);
                    });
            });
        },
        //获取图片的高度
        async setPosterData() {
            const self = this;
            if (!self.textHeightCtx) {
                const textHeightCtx = await self.getTextHeightCtx();
                self.textHeightCtx = textHeightCtx;
            }
            //作品详情页
            if (self.data.currentPage.route === 'pages/details/work/work') {
                import('../page-poster-data/work')
                    .then(({ createPosterData }) => {
                        createPosterData(self.textHeightCtx, self.data.shareData)
                            .then(res => {
                                self.setData({
                                    posterData: res,
                                });
                            })
                            .catch(error => {
                                wx.hideLoading();
                                self.data.isCreating = false;
                                self.data.clickedCreateBtn = false;
                                wx.showToast({
                                    title: (error && error.errMsg) || '生成失败,请重试',
                                    icon: 'none',
                                });
                            });
                    })
                    .catch(error => {
                        console.log('createPosterData 失败', error);
                        wx.hideLoading();
                        self.data.isCreating = false;
                        this.data.clickedCreateBtn = false;
                    });
            }
        },
        // 保存图片到本地
        savePoster() {
            this.isWritePhotosAlbum();
        },
        //判断是否授权(访问相册)
        isWritePhotosAlbum: function () {
            let self = this;
            //判断是否授权
            wx.getSetting({
                success: function (res) {
                    let writePhotosAlbum = res.authSetting['scope.writePhotosAlbum'];
                    if (writePhotosAlbum) {
                        //已授权 //true
                        self.saveImageToPhoto();
                    } else if (writePhotosAlbum != undefined && writePhotosAlbum != true) {
                        //拒绝授权了 //false
                        self.data.savePhotoIng = false;
                        wx.showModal({
                            title: '',
                            content: '您还未授权保存图片到相册,请确认授权',
                            showCancel: true,
                            cancelText: '取消',
                            confirmText: '确认',
                            success: function (e) {
                                //点了查看规则
                                if (e.confirm) {
                                    //针对用户保存图片的时候可能会拒绝授权,再次点击时需要调起授权窗口
                                    self.openSetting();
                                } else {
                                    //取消
                                }
                            },
                        });
                    } else {
                        //第一次授权 //undefined
                        self.saveImageToPhoto();
                    }
                },
            });
        },
        //授权操作(访问相册)
        openSetting: function () {
            let self = this;
            //调起授权弹窗
            wx.openSetting({
                success(res) {
                    //同意授权
                    if (res.authSetting['scope.writePhotosAlbum']) {
                        self.saveImageToPhoto();
                    }
                },
            });
        },
        //保存图片到相册
        saveImageToPhoto: function () {
            let self = this;
            const tempFilePath = self.data.tempFilePath;
            wx.saveImageToPhotosAlbum({
                filePath: tempFilePath,
                success(res) {
                    wx.showToast({
                        title: '保存成功',
                        icon: 'success',
                    });
                    self.setData({
                        show: false,
                    });
                },
                fail: function (res) {
                    if (res.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
                        toast('您拒绝了授权无法保存图片 ');
                    } else {
                        if (
                            res.errMsg === 'saveImageToPhotosAlbum:fail:auth canceled' ||
                            res.errMsg === 'saveImageToPhotosAlbum:fail cancel'
                        ) {
                            console.log('用户取消');
                        } else {
                            wx.showToast({
                                title: '保存失败,请重试',
                                icon: 'none',
                            });
                        }
                    }
                },
                complete: function (res) {
                    console.log('保存图片到本地 结束', res);
                },
            });
        },
        onImgOK(e) {
            console.log('onImgOK', e);
            this.data.isCreating = false;
            this.data.tempFilePath = e.detail.path;
            this.setData({
                tempFilePath: this.data.tempFilePath,
            });
            this.triggerEvent('posterStatus', e.detail.path);
            wx.hideLoading();
            if (this.data.clickedCreateBtn) {
                this.setData({
                    show: true,
                });
                this.data.clickedCreateBtn = false;
            }
        },
        onImgErr(err) {
            this.data.isCreating = false;
            console.log('onImgErr', err);
            wx.hideLoading();
        },
        closePreviewPop() {
            this.setData({
                show: false,
            });
        },
        //预览海报图片加载完成
        filePathLoaded(event) {
            const { width, height } = event.detail;
            //计算出预览海报的在页面中的展示高度
            const scaleImgHeight = Math.floor((this.data.screenWidth * 0.67 * height) / width);
            this.data.imgHeight =
                scaleImgHeight > this.data.maxImgHeight ? this.data.maxImgHeight : scaleImgHeight;
            this.setData({
                imgHeight: this.data.imgHeight,
            });
        },
        filePathError(event) {
            console.log('图片加载失败error', event);
        },
    },
});

less样式:

.preview-poster-container {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1002;
    .shade {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.6);
        z-index: 1;
    }
    .content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 67%;
        max-height: 90%;
        z-index: 2;
        //关闭按钮不算在垂直居中的里面 margint-top值为关闭按钮的高度的一半
        margin-top: -34rpx;
        .close-bar {
            text-align: right;
            width: 100%;
        }
        .close-btn {
            display: inline-block;
            padding: 20rpx 30rpx;
            margin-right: -30rpx;
        }
        .close-icon {
            width: 28rpx;
            height: 28rpx;
        }
        .poster-box {
            width: 100%;
            overflow: auto;
        }
        .poster {
            width: 100%;
            height: auto;
        }
        .save-btn {
            display: block;
            width: 400rpx;
            height: 112rpx;
            background: rgba(0, 0, 0, 0.9);
            color: #fff;
            text-align: center;
            line-height: 111rpx;
            margin: 44rpx auto 0 auto;
            font-size: 32rpx;
            font-weight: 500;
            letter-spacing: 3rpx;
            border-radius: 99rpx;
        }
    }
}

page-poster-data文件

work.js

import shareUtils from '../share-utils';
//获取图片的高度 750rpx对应的高度
const app = getApp();
const createPosterData = (ctx, shareData) => {
    return new Promise(async (resolve, reject) => {
        const { showImg, title, qrCodeImg, subordinateTitle, designerHeadPortrait, designerName } =
            shareData;
        const titleView = {
            type: 'text',
            text: title,
            css: {
                left: '48rpx',
                width: '450rpx',
                fontWeight: '500',
                color: '#fff',
                fontSize: '50rpx',
                lineHeight: '72rpx',
                scalable: true,
                maxLines: 2,
            },
        };
        const subordinateTitleView = {
            type: 'text',
            text: subordinateTitle,
            css: {
                left: '48rpx',
                bottom: '334rpx',
                width: '450rpx',
                color: '#fff',
                fontSize: '26rpx',
                lineHeight: '40rpx',
                scalable: true,
                maxLines: 2,
            },
        };
        let scaleImgHeight = 0;
        try {
            const imgPositionData = await shareUtils.getImgPositionData(showImg);
            if (imgPositionData) {
                scaleImgHeight = imgPositionData.scaleHeight;
            }
        } catch (error) {
            reject(error);
            return;
        }

        let titleH = 0;
        const titlePositionData = await shareUtils.getTextPositionData({ ctx, view: titleView });
        if (titlePositionData) {
            titleH = shareUtils.toRpx(titlePositionData.height);
        }
        let subordinateTitleH = 0;
        const subordinateTitlePositionData = await shareUtils.getTextPositionData({
            ctx,
            view: subordinateTitleView,
        });

        if (subordinateTitlePositionData) {
            subordinateTitleH = shareUtils.toRpx(subordinateTitlePositionData.height);
        }
        titleView.css.bottom = `${334 + subordinateTitleH + 40}rpx`;
        //底部padding值
        const paddingBottom = 338; //从副标题往下的距离 rpx
        const titleDistance = 40; //主标题到副标题之间的距离 rpx

        const posterHeight = Math.ceil(
            scaleImgHeight + (titleH * 2) / 3 + titleDistance + subordinateTitleH + paddingBottom
        );
        const distanceTop = Math.ceil(scaleImgHeight * 0.38); // 渐变色距离顶部的距离 rpx
        //渐变背景的高度
        const linearGradientHeight = Math.ceil(
            distanceTop + (titleH * 2) / 3 + titleDistance + subordinateTitleH + paddingBottom
        );
        const startLinear =
            Math.ceil((1 - distanceTop / linearGradientHeight).toFixed(2) * 100) + '%';
        // 宽度和高度必须 位置必须rpx才能生效,否则默认左上角 0 0 位置 渐变后面的百分数必须写
        const data = {
            width: '750rpx',
            height: `${posterHeight}rpx`,
            background: '#060419',
            views: [
                // 顶栏图片
                {
                    type: 'image',
                    url: showImg,
                    css: {
                        top: '0rpx',
                        left: '0rpx',
                        width: '750rpx',
                        mode: 'widthFix',
                        scalable: true,
                    },
                },
                //渐变矩形 //不认 to bottom 、to top等格式
                {
                    type: 'rect',
                    css: {
                        left: '0rpx',
                        bottom: '0rpx',
                        width: '750rpx',
                        height: `${linearGradientHeight}rpx`,
                        //从下向上的渐变
                        color: `linear-gradient(180deg, rgba(6, 4, 25, 1) 0%, rgba(7,4,26,1) ${startLinear}, transparent 100%)`,
                        scalable: true,
                    },
                },

                //标题
                titleView,
                // 副标题
                subordinateTitleView,
                //分割线
                {
                    type: 'rect',
                    css: {
                        left: '50rpx',
                        bottom: '270rpx',
                        width: '320rpx',
                        height: '2rpx',
                        // opacity: '0.3',
                        // color: '#C4C4C4', //rect 下背景色用color
                        color: 'rgba(196, 196, 196, 0.3)',
                    },
                },
                //设计师
                {
                    type: 'text',
                    text: '设计师 ',
                    css: {
                        left: '50rpx',
                        bottom: '200rpx',
                        color: '#fff',
                        fontSize: '28rpx',
                        align: 'left',
                        scalable: true,
                        lineHeight: '40rpx',
                    },
                },
                // 设计师头像
                {
                    type: 'image',
                    url: designerHeadPortrait,
                    css: {
                        bottom: '142rpx',
                        left: '50rpx',
                        width: '42rpx',
                        height: '42rpx',
                        scalable: true,
                        borderRadius: '4rpx',
                    },
                },
                //设计师姓名
                {
                    type: 'text',
                    text: designerName,
                    css: {
                        left: '110rpx',
                        bottom: '142rpx',
                        color: '#fff',
                        fontSize: '28rpx',
                        align: 'left',
                        scalable: true,
                        lineHeight: '42rpx',
                    },
                },
                // 优秀作品 即可点击收藏
                {
                    type: 'text',
                    text: '优秀作品,即刻点击收藏',
                    css: {
                        left: '50rpx',
                        bottom: '78rpx',
                        color: 'rgba(255,255,255,0.5)',
                        fontSize: '28rpx',
                        scalable: true,
                        lineHeight: '40rpx',
                    },
                },
                // 二维码
                {
                    type: 'image',
                    // url: codeImg,
                    url: qrCodeImg,
                    css: {
                        bottom: `50rpx`,
                        right: '48rpx',
                        backgroundColor: '#fff',
                        width: '160rpx',
                        height: '160rpx',
                        scalable: true,
                        borderRadius: '160rpx',
                    },
                },
            ],
        };
        resolve(data);
    });
};
module.exports.createPosterData = createPosterData;

share-utils js

公共share-utils js文件,计算文字高度、px/rpx简单换算等:

//获取text类型的行高 行数等
const screenWidth = wx.getSystemInfoSync().screenWidth;
/*
    text:文案
    fontSize:字体大小 只支持rpx 数值类型
    maxLines: 文案最大行数  可不传 数值类型
    width: 文案宽度  可不传 只支持rpx 数值类型
    padding: 文案padding值为数组格式 只支持rpx值 可不传

*/
const getTextPositionData = ({ ctx, view }) => {
    let paddings = doPaddings(view.css.padding);
    const textArray = String(view.text).split('\n');
    // 处理多个连续的'\n'
    for (let i = 0; i < textArray.length; ++i) {
        if (textArray[i] === '') {
            textArray[i] = ' ';
        }
    }
    if (!view.css.fontSize) {
        view.css.fontSize = '20rpx';
    }
    const fontWeight = view.css.fontWeight || '400';
    const textStyle = view.css.textStyle || 'normal';
    ctx.font = `${textStyle} ${fontWeight} ${toPx(view.css.fontSize)}px "${
        view.css.fontFamily || 'sans-serif'
    }"`;
    // 计算行数
    let lines = 0;
    // let totalWidth = 0
    const linesArray = [];
    for (let i = 0; i < textArray.length; ++i) {
        const textLength = ctx.measureText(textArray[i]).width;
        const minWidth = toPx(view.css.fontSize) + paddings[1] + paddings[3];
        let partWidth = view.css.width
            ? toPx(view.css.width) - paddings[1] - paddings[3]
            : textLength;
        if (partWidth < minWidth) {
            partWidth = minWidth;
        }
        const calLines = Math.ceil(textLength / partWidth);
        // 取最长的作为 width
        // totalWidth = partWidth > totalWidth ? partWidth : totalWidth;
        lines += calLines;
        linesArray[i] = calLines;
    }
    lines = view.css.maxLines < lines ? view.css.maxLines : lines;
    const lineHeight = view.css.lineHeight ? toPx(view.css.lineHeight) : toPx(view.css.fontSize);
    const height = lineHeight * lines;
    return {
        lines, //行数
        height, //高度
    };
};
//计算rpx px转换用
const toPx = (rpx, int, factor = screenWidth / 750, pixelRatio = 1) => {
    rpx = rpx.replace('rpx', '');
    if (int) {
        return parseInt(rpx * factor * pixelRatio);
    }
    return rpx * factor * pixelRatio;
};
const toRpx = (px, int, factor = screenWidth / 750) => {
    if (int) {
        return parseInt(px / factor);
    }
    return px / factor;
};
const doPaddings = padding => {
    let pd = [0, 0, 0, 0];
    if (padding) {
        const pdg = padding.split(/\s+/);
        if (pdg.length === 1) {
            const x = toPx(pdg[0]);
            pd = [x, x, x, x];
        }
        if (pdg.length === 2) {
            const x = toPx(pdg[0]);
            const y = toPx(pdg[1]);
            pd = [x, y, x, y];
        }
        if (pdg.length === 3) {
            const x = toPx(pdg[0]);
            const y = toPx(pdg[1]);
            const z = toPx(pdg[2]);
            pd = [x, y, z, y];
        }
        if (pdg.length === 4) {
            const x = toPx(pdg[0]);
            const y = toPx(pdg[1]);
            const z = toPx(pdg[2]);
            const a = toPx(pdg[3]);
            pd = [x, y, z, a];
        }
    }
    return pd;
};
// 获取图片宽高以及750像素下的高度
export const getImgPositionData = img => {
    return new Promise((resolve, reject) => {
        if (img) {
            wx.getImageInfo({
                src: img,
                success(res) {
                    const { width, height } = res;
                    const scaleHeight = Math.ceil((750 * height) / width);
                    resolve({
                        width,
                        height,
                        scaleHeight,
                    });
                },
                fail() {
                    reject({
                        errMsg: '获取图片信息失败, 请重试',
                    });
                },
            });
        } else {
            // wx.hideLoading();
            reject({
                errMsg: '图片不存在',
            });
        }
    });
};
export default {
    getTextPositionData,
    getImgPositionData,
    toPx,
    toRpx,
};

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值