uniapp 项目实践总结(九)自定义分享组件

这篇文章主要是讲述自定义分享组件的方放,兼容网页 H5 端、微信小程序端和 App 端。

# 目录

  • 准备工作
  • 原理分析
  • 组件实现
  • H5 和 App
  • 小程序
  • 案例展示

# 准备工作

  • 首先我们从图标图库网站上面找一些常用的分享图标并下载下来;
  • components新建一个q-share文件夹,并新建一个q-share.vue的组件;
  • 按照前一篇所说的页面结构,编写好预定的分享页面;

# 原理分析

自定义分享组件就是采用各端支持的 API 方法进行封装设置,利用条件编译的语法进行分而实现。

# H5 端分析

H5 端主要有两种分享方法:

  • 普通浏览器自带分享按钮;微信内嵌浏览器,可调用js-sdk (opens new window)进行分享;
  • 第三方平台网页应用:在第三方分享平台申请添加自己的应用,以便调用第三方的分享 API 进行分享;
  • 网上公共分享链接:这里就使用网上公开的分享地址进行分享了。

以下罗列了几个常用的第三方分享平台的地址,感兴趣的可以收藏看一下。

常用开放平台:

# 小程序端分析

小程序端的分享目前 uniapp 不支持 API 调用,只能用户主动点击触发分享,可使用自定义按钮方式<button open-type="share">或监听系统右上角的分享按钮onShareAppMessage 进行自定义分享内容。

在这里可以查看微信小程序的分享 API (opens new window)进行小程序分享功能开发。

小程序文档:

# APP 端分析

APP 端的分享可以自主控制分享的内容、形式及平台,提供了以下两种方法:

开发者官网:

快应用官网:

# 组件实现

准备工作和原理分析完成后,接下来写一个简单的自定义分享组件。

# 模板部分

<view class="q-share" v-if="shares.show">
  <view :class="{'q-share-mak': true, 'active': shares.showMask}" @click="close('mask')"></view>
  <view
    :class="{'q-share-inner': true, [shares.options.dir]: true, 'active': shares.showBox}"
    :style="{'width': `${['left', 'right'].includes(shares.options.dir) ? shares.options.width + 'rpx' : '100%'}`, 'height': `${['up', 'down'].includes(shares.options.dir) ? shares.options.height + 'rpx' : '100%'}`, borderRadius: shares.options.radius+'rpx'}">
    <slot name="box">
      <view class="q-share-box">
        <view class="q-share-title"> 系统分享 </view>
        <scroll-view :scroll-x="true" class="q-share-content">
          <view class="q-share-list" :style="{'width': shareList.length*145+'rpx'}">
            <view
              class="q-share-item"
              v-for="item in shareList"
              :key="item.id"
              @click="shareSet(item)">
              <q-icon class="q-share-item-icon" :name="item.val" :size="20" />
              <text class="q-share-item-txt">{{item.name}}</text>
            </view>
          </view>
        </scroll-view>
        <view class="q-share-cancel" @click="close('cancel')"> 取消分享 </view>
      </view>
    </slot>
  </view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 样式部分

.q-share {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100vh;
  z-index: 199;
  .q-share-mak {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.45);
    transition: background 2s;
    z-index: 90;
    &.active {
      background: rgba(0, 0, 0, 0.35);
    }
  }
  .q-share-inner {
    position: absolute;
    max-width: 100%;
    max-height: 100%;
    background: rgba(255, 255, 255, 0.95);
    transition: all 0.5s;
    z-index: 95;
    .q-share-box {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      box-sizing: border-box;
      padding: 15rpx 25rpx;
      width: 100%;
      height: 100%;
      .q-share-title {
        width: 100%;
        line-height: 3;
        font-size: 28rpx;
        color: $uni-text-color;
        text-align: center;
      }
      .q-share-content {
        flex: 1;
        box-sizing: border-box;
        padding: 20rpx;
        width: 100%;
        height: calc(100% - 140rpx);
        .q-share-list {
          display: flex;
          flex-flow: row nowrap;
          box-sizing: border-box;
          padding: 25rpx 0;
          height: 100%;
          .q-share-item {
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            width: 145rpx;
            height: 100%;
            .q-share-item-icon {
              padding: 0;
            }
            .q-share-item-txt {
              margin-top: 10rpx;
              font-size: 24rpx;
            }
          }
        }
      }
      .q-share-cancel {
        width: 100%;
        line-height: 2;
        font-size: 28rpx;
        color: $uni-text-color-grey;
        text-align: center;
      }
    }
    &.down,
    &.up {
      left: 0;
    }
    &.down {
      bottom: 0;
      border-bottom-left-radius: 0 !important;
      border-bottom-right-radius: 0 !important;
      transform: translateY(100%);
      &.active {
        transform: translateY(0);
      }
    }
    &.up {
      top: 0;
      border-top-left-radius: 0 !important;
      border-top-right-radius: 0 !important;
      transform: translateY(-100%);
      &.active {
        transform: translateY(0);
      }
    }
    &.left,
    &.right {
      top: 0;
      width: 0;
    }
    &.left {
      left: 0;
      border-top-left-radius: 0 !important;
      border-bottom-left-radius: 0 !important;
      transform: translateX(-100%);
      &.active {
        transform: translateX(0);
      }
    }
    &.right {
      right: 0;
      border-top-right-radius: 0 !important;
      border-bottom-right-radius: 0 !important;
      transform: translateX(100%);
      &.active {
        transform: translateX(0);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

# 脚本部分

  • 引入依赖包和属性设置
import { ref, reactive, computed } from "vue";

// 属性
const showTimer = ref(null); // 显示延迟
const showTime = ref(100); // 显示延迟时间
const showCloseTimer = ref(null); // 显示关闭延迟
const showCloseTime = ref(300); // 显示关闭延迟时间

const shares = reactive({
  show: false, // 显示分享框
  showMask: false, // 显示模态框
  showBox: false, // 显示内容动画
  options: {
    // 父组件消息
    dir: "down", // 内容方向
    radius: 0, // 圆角角度
    width: 200, // 内容宽度
    height: 300, // 内容高度
  },
  list: [
    {
      id: 1,
      name: "QQ",
      val: "qq",
      url(url, title) {
        return `https://connect.qq.com/widget/shareqq/iframe_index.html?url=${url}&title=${title}`;
      },
    },
    {
      id: 2,
      name: "QQ空间",
      val: "qzone",
      url(url, title) {
        return `http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${url}&title=${title}&site=${url}`;
      },
    },
    {
      id: 3,
      name: "微博",
      val: "weibo",
      url(url, title) {
        return `http://service.weibo.com/share/share.php?url=${url}&title=${title}&language=zh_cn`;
      },
    },
    {
      id: 4,
      name: "微信",
      val: "wechat",
      url() {
        return "";
      },
    },
    {
      id: 5,
      name: "百度",
      val: "baidu",
      url() {
        return "";
      },
    },
    {
      id: 6,
      name: "贴吧",
      val: "tieba",
      url() {
        return "";
      },
    },
    {
      id: 7,
      name: "朋友圈",
      val: "friend",
      url() {
        return "";
      },
    },
    {
      id: 8,
      name: "淘宝",
      val: "taobao",
      url() {
        return "";
      },
    },
    {
      id: 9,
      name: "支付宝",
      val: "alipay",
      url() {
        return "";
      },
    },
    {
      id: 10,
      name: "钉钉",
      val: "dingtalk",
      url() {
        return "";
      },
    },
    {
      id: 11,
      name: "快手",
      val: "gifks",
      url() {
        return "";
      },
    },
    {
      id: 12,
      name: "Whatsapp",
      val: "whatsapp",
      url() {
        return "";
      },
    },
    {
      id: 13,
      name: "Messenger",
      val: "messenger",
      url() {
        return "";
      },
    },

    {
      id: 14,
      name: "Youtube",
      val: "youtube",
      url() {
        return "";
      },
    },
    {
      id: 15,
      name: "Instagram",
      val: "instagram",
      url() {
        return "";
      },
    },
    {
      id: 16,
      name: "Twitter",
      val: "twitter",
      url() {
        return "";
      },
    },
    {
      id: 17,
      name: "Telegram",
      val: "telegram",
      url() {
        return "";
      },
    },
    {
      id: 18,
      name: "复制链接",
      val: "link",
      url() {
        return location.href;
      },
    },
    {
      id: 19,
      name: "更多",
      val: "more",
      url() {
        return "";
      },
    },
  ],
  info: {
    title: "", // 分享标题
    url: "", // 分享地址
  },
});

const emits = defineEmits(["close", "share"]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
  • 分享方法定义
// 方法

// 分享列表
const shareList = computed(() => {
  let newList = [],
    mnp = ["qq", "qzone", "wechat", "friend"];
  // #ifdef H5
  newList = shares.list.filter((s) => s.val != "more");
  // #endif
  // #ifdef MP-WEIXIN
  newList = shares.list.filter((s) => mnp.includes(s.val));
  // #endif
  // #ifdef APP-PLUS
  newList = shares.list;
  // #endif
  return newList;
});

// 打开
function open(options) {
  let defaultOptions = {
    dir: "down",
    radius: 0,
    width: 500,
    height: 300,
    title: "",
    url: "",
  };
  let params = { ...defaultOptions, ...options };
  shares.show = true;
  shares.options = { ...params };
  shares.info = {
    title: params.title,
    url: params.url,
  };
  showTimer.value = setTimeout(() => {
    shares.showMask = true;
    shares.showBox = true;
    clearTimeout(showTimer.value);
  }, showTime.value);
}

// 关闭
function close(from = "") {
  shares.showBox = false;
  emits("close", from);
  showTimer.value = setTimeout(() => {
    shares.showMask = false;
    clearTimeout(showTime.value);
  }, showTime.value);
  showCloseTimer.value = setTimeout(() => {
    shares.show = false;
    clearTimeout(showCloseTimer.value);
  }, showCloseTime.value);
}

// 分享出去
function shareSet(info) {
  // 通知父组件
  emits("share", info);

  // 分享内容
  let url = shares.info.url,
    title = shares.info.title;

  // 网页分享
  // #ifdef H5
  url = url || location.href;
  title = title || document.title;
  // #endif

  // 分享提示
  if (info.val == "more") {
    // #ifdef APP-PLUS
    uni.shareWithSystem({
      type: "text",
      summary: title,
      href: url,
      success() {
        uni.showToast({
          title: "分享成功!",
          icon: "success",
        });
      },
      fail() {
        uni.showToast({
          title: "分享失败!",
          icon: "error",
        });
      },
    });
    // #endif
  } else if (info.val == "link") {
    uni.setClipboardData({
      data: url,
      success() {
        uni.showToast({
          title: "复制成功!",
          icon: "success",
        });
      },
    });
  } else {
    let shareUrl = info.url(url, title);
    if (shareUrl) {
      // #ifdef H5
      window.open(shareUrl);
      // #endif
      // #ifdef APP-PLUS
      if (plus) {
        plus.runtime.openURL(shareUrl, (res) => {
          console.log("shareUrl res:", res);
        });
      }
      // #endif
    } else {
      // #ifdef APP-PLUS
      const scene = "WXSceneSession",
        params = {
          href: "https://blog.example.com",
          title: "列表页面",
          summary: "我正在使用HelloApp,赶紧跟我一起来体验!",
          imageUrl: "https://blog.example.com/img/03.png",
        };
      // 分享到聊天界面
      if (info.val == "wechat") {
        appShareWx(scene, params);
      }
      // 分享到朋友圈
      if (info.val == "friend") {
        scene = "WXSceneTimeline";
        appShareWx(scene, params);
      }
      // #endif
      uni.showToast({
        title: "分享成功!",
        icon: "success",
      });
    }
  }

  // 关闭分享
  close();
}

// #ifdef APP-PLUS
// 分享到聊天界面或朋友圈
function appShareWx(scene = "WXSceneSession", info) {
  let { href, title, summary, imageUrl } = info;
  uni.share({
    provider: "weixin",
    scene,
    type: 0,
    href,
    title,
    summary,
    imageUrl,
    success: function (res) {
      console.log("success:" + JSON.stringify(res));
    },
    fail: function (err) {
      console.log("fail:" + JSON.stringify(err));
    },
  });
}
// #endif

defineExpose({
  open,
  close,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

# H5 和 App

写好分享组件后,可以在 H5 和 App 里面使用了,下面是页面模板和脚本使用方法。

# 模板使用

<!-- 顶部导航组件引入 -->
<q-navbar :center="navConfig.center" :right="navConfig.right" @change="changeNav" />
<!-- 分享组件引入 -->
<q-share ref="mineShare" @share="shareSetting" />
1
2
3
4

# 脚本使用

点击右上角分享图标,调用下面方法进行分享。

  • 定义数据
// 顶部导航配置
const navConfig = reactive({
  center: {
    show: true,
    name: "列表页面",
  },
  right: {
    show: true,
    icon: "more",
    type: "self",
  },
});

// 分享信息配置
let shareInfo = reactive({
  dir: "down",
  radius: 20,
  height: 350,
  title: "列表页面",
  url: "https://blog.example.com/#/pages/index/list?from=share",
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 方法调用
// 点击导航分享图标调用分享方法
function changeNav(info) {
  if (info.from == "right" && info.icon == "more") {
    if (proxy.$refs.mineShare) {
      proxy.$refs.mineShare.open(shareInfo);
    }
  }
}

// 分享组件返回分享信息
function shareSetting(info) {
  console.log("分享消息:", info); // 分享消息: Proxy(Object) {id: 4, name: '微信', val: 'wechat', url: ƒ}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 小程序

在这里特别说明一下小程序,由于 uniapp 不支持小程序的 API,因此这里根据小程序的文档封装了一个获取小程序参数的方法放在scriptsutils文件中。

# 获取参数

// 获取微信分享信息
function getWxShare(options) {
  let pages = getCurrentPages(),
    page = pages[pages.length - 1],
    defaultOptions = {
      title: "HelloApp",
      path: `/${page.route}`,
      imageUrl: "",
      query: "from=list",
      promise: null,
      type: "message", // message 聊天界面,friend 朋友圈,favorite 收藏
      res: {},
    };
  let params = {
    ...defaultOptions,
    ...options,
  };
  const { title, imageUrl, path, query, promise } = params;
  const message = {
    path: `${path}?${query}`,
    imageUrl,
    title,
    promise,
  };
  const friend = {
    path,
    query,
    imageUrl,
    title,
  };
  const favorite = {
    title,
    imageUrl,
    query,
  };
  const shares = {
    message,
    friend,
    favorite,
  };
  const result = shares[params.type];
  console.log(`获取微信小程序分享参数${params.type}:`, result);
  return params;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 引入 API

接着就是页面引入 uniapp 的 API 和微信分享方法。

import { onLoad, onShareAppMessage, onShareTimeline, onAddToFavorites } from "@dcloudio/uni-app";
1

# 获取参数

onLoad((option) => {
  // #ifdef MP-WEIXIN
  // 设置微信分享
  setWxShare();
  // #endif
});

// #ifdef MP-WEIXIN
// 设置微信分享
function setWxShare(type = "message") {
  const params = proxy.$apis.utils.getWxShare({
    title: "列表页面",
    query: "from=list",
    type,
  });
  return params;
}
// #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 监听转发事件

// #ifdef MP-WEIXIN
// 转发到聊天界面
onShareAppMessage((res) => {
  const params = setWxShare("message");
  return params;
});

// 转发到朋友圈
onShareTimeline((res) => {
  const params = setWxShare("friend");
  return params;
});

// 添加到收藏
onAddToFavorites((res) => {
  const params = setWxShare("favorite");
  return params;
});
// #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 案例展示

  • h5 端效果

h5 端效果

  • 小程序端效果

小程序端效果

  • APP 端效果

APP 端效果

# 最后

以上就是自定义分享组件的主要内容,有不足之处,请多多指正。

分享至:

  • qq
  • qq空间
  • 微博
  • 豆瓣
  • 贴吧