|
|
@@ -0,0 +1,400 @@
|
|
|
+<template>
|
|
|
+ <view v-if="visible" class="share-overlay" @click="close">
|
|
|
+ <view class="share-mask" />
|
|
|
+ <view class="share-panel" :class="{ 'h5-panel': platform === 'h5' }" @click.stop>
|
|
|
+ <!-- 面板头部 -->
|
|
|
+ <view class="panel-header">
|
|
|
+ <text class="panel-title">分享到</text>
|
|
|
+ <view class="panel-close" @click="close">
|
|
|
+ <text>✕</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 分享渠道列表 -->
|
|
|
+ <view class="share-channels">
|
|
|
+ <!-- H5 端:浏览器原生分享 -->
|
|
|
+ <view v-if="platform === 'h5'" class="channel-grid">
|
|
|
+ <view class="channel-item" @click="doWebShare">
|
|
|
+ <view class="channel-icon" style="background: linear-gradient(135deg, #667eea, #764ba2)">
|
|
|
+ <text class="icon-emoji">📤</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">分享给好友</text>
|
|
|
+ </view>
|
|
|
+ <view class="channel-item" @click="doCopyLink">
|
|
|
+ <view class="channel-icon" style="background: #6b7280">
|
|
|
+ <text class="icon-emoji">🔗</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">复制链接</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="showPosterBtn" class="channel-item" @click="$emit('generatePoster')">
|
|
|
+ <view class="channel-icon" style="background: linear-gradient(135deg, #f093fb, #f5576c)">
|
|
|
+ <text class="icon-emoji">🖼️</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">生成海报</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- App 端:原生分享渠道 -->
|
|
|
+ <view v-if="platform === 'app'" class="channel-grid">
|
|
|
+ <view class="channel-item" @click="doShareToWechat">
|
|
|
+ <view class="channel-icon wechat">
|
|
|
+ <text class="icon-emoji">💬</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">微信好友</text>
|
|
|
+ </view>
|
|
|
+ <view class="channel-item" @click="doShareToTimeline">
|
|
|
+ <view class="channel-icon timeline">
|
|
|
+ <text class="icon-emoji">🌐</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">朋友圈</text>
|
|
|
+ </view>
|
|
|
+ <view class="channel-item" @click="doShareToQQ">
|
|
|
+ <view class="channel-icon qq">
|
|
|
+ <text class="icon-emoji">🐧</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">QQ好友</text>
|
|
|
+ </view>
|
|
|
+ <view class="channel-item" @click="doShareWithSystem">
|
|
|
+ <view class="channel-icon" style="background: #374151">
|
|
|
+ <text class="icon-emoji">📲</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">更多</text>
|
|
|
+ </view>
|
|
|
+ <view class="channel-item" @click="doCopyLink">
|
|
|
+ <view class="channel-icon" style="background: #6b7280">
|
|
|
+ <text class="icon-emoji">🔗</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">复制链接</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="showPosterBtn" class="channel-item" @click="$emit('generatePoster')">
|
|
|
+ <view class="channel-icon poster">
|
|
|
+ <text class="icon-emoji">🖼️</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">生成海报</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 小程序端:分享消息卡片 + 复制链接 -->
|
|
|
+ <view v-if="platform === 'mp-weixin'" class="channel-grid">
|
|
|
+ <!-- 小程序分享消息 -->
|
|
|
+ <button class="channel-item mp-share-btn" open-type="share" @click="onMiniProgramShare">
|
|
|
+ <view class="channel-icon wechat">
|
|
|
+ <text class="icon-emoji">💬</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">发送给朋友</text>
|
|
|
+ </button>
|
|
|
+ <view class="channel-item" @click="doCopyLink">
|
|
|
+ <view class="channel-icon" style="background: #6b7280">
|
|
|
+ <text class="icon-emoji">🔗</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">复制链接</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="showPosterBtn" class="channel-item" @click="$emit('generatePoster')">
|
|
|
+ <view class="channel-icon poster">
|
|
|
+ <text class="icon-emoji">🖼️</text>
|
|
|
+ </view>
|
|
|
+ <text class="channel-label">生成海报</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 面板底部 -->
|
|
|
+ <view class="panel-footer">
|
|
|
+ <text class="footer-tip">分享给好友,一起听好书</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted } from 'vue';
|
|
|
+import type { ShareInfo } from '../utils/share';
|
|
|
+import {
|
|
|
+ getSharePlatform,
|
|
|
+ shareToWechatSession,
|
|
|
+ shareToWechatTimeline,
|
|
|
+ shareToQQ,
|
|
|
+ shareWithSystem,
|
|
|
+ copyShareLink,
|
|
|
+ webShareApi,
|
|
|
+ trackShareAction,
|
|
|
+} from '../utils/share';
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ visible: boolean;
|
|
|
+ shareInfo: ShareInfo;
|
|
|
+ /** 专辑/章节 ID,用于记录分享行为 */
|
|
|
+ audioId?: number;
|
|
|
+ /** 是否显示生成海报按钮 */
|
|
|
+ showPosterBtn?: boolean;
|
|
|
+}>();
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ (e: 'close'): void;
|
|
|
+ (e: 'generatePoster'): void;
|
|
|
+ /** 小程序分享时触发,父组件需在 onShareAppMessage 中使用 */
|
|
|
+ (e: 'miniProgramShare', info: ShareInfo): void;
|
|
|
+ (e: 'shareSuccess', channel: string): void;
|
|
|
+ (e: 'shareFail', err: any): void;
|
|
|
+}>();
|
|
|
+
|
|
|
+const platform = ref<'app' | 'mp-weixin' | 'h5'>('h5');
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ platform.value = getSharePlatform();
|
|
|
+});
|
|
|
+
|
|
|
+function close() {
|
|
|
+ emit('close');
|
|
|
+}
|
|
|
+
|
|
|
+function handleShareSuccess(channel: string) {
|
|
|
+ if (props.audioId) {
|
|
|
+ trackShareAction(props.audioId, channel);
|
|
|
+ }
|
|
|
+ emit('shareSuccess', channel);
|
|
|
+}
|
|
|
+
|
|
|
+function handleShareError(err: any) {
|
|
|
+ console.error('[SharePanel] 分享失败:', err);
|
|
|
+ emit('shareFail', err);
|
|
|
+ uni.showToast({ title: '分享失败,请重试', icon: 'none' });
|
|
|
+}
|
|
|
+
|
|
|
+// App: 微信好友
|
|
|
+async function doShareToWechat() {
|
|
|
+ try {
|
|
|
+ await shareToWechatSession(props.shareInfo);
|
|
|
+ handleShareSuccess('wechat-session');
|
|
|
+ } catch (err) {
|
|
|
+ handleShareError(err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// App: 朋友圈
|
|
|
+async function doShareToTimeline() {
|
|
|
+ try {
|
|
|
+ await shareToWechatTimeline(props.shareInfo);
|
|
|
+ handleShareSuccess('wechat-timeline');
|
|
|
+ } catch (err) {
|
|
|
+ handleShareError(err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// App: QQ
|
|
|
+async function doShareToQQ() {
|
|
|
+ try {
|
|
|
+ await shareToQQ(props.shareInfo);
|
|
|
+ handleShareSuccess('qq');
|
|
|
+ } catch (err) {
|
|
|
+ handleShareError(err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// App: 系统分享
|
|
|
+async function doShareWithSystem() {
|
|
|
+ try {
|
|
|
+ await shareWithSystem(props.shareInfo);
|
|
|
+ handleShareSuccess('system');
|
|
|
+ } catch (err) {
|
|
|
+ handleShareError(err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 复制链接 (全平台)
|
|
|
+async function doCopyLink() {
|
|
|
+ try {
|
|
|
+ await copyShareLink(props.shareInfo);
|
|
|
+ handleShareSuccess('copy-link');
|
|
|
+ close();
|
|
|
+ } catch (err) {
|
|
|
+ handleShareError(err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// H5: 浏览器原生分享
|
|
|
+async function doWebShare() {
|
|
|
+ try {
|
|
|
+ await webShareApi(props.shareInfo);
|
|
|
+ handleShareSuccess('web-share');
|
|
|
+ close();
|
|
|
+ } catch (err) {
|
|
|
+ // 降级为复制链接
|
|
|
+ doCopyLink();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 小程序: 发送给朋友
|
|
|
+function onMiniProgramShare() {
|
|
|
+ emit('miniProgramShare', props.shareInfo);
|
|
|
+ handleShareSuccess('mp-share');
|
|
|
+ close();
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.share-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 2000;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.share-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ animation: fadeIn 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.share-panel {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 500px;
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 32rpx 32rpx 0 0;
|
|
|
+ padding: 0 0 env(safe-area-inset-bottom);
|
|
|
+ animation: slideUp 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 32rpx 32rpx 24rpx;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-title {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-close {
|
|
|
+ position: absolute;
|
|
|
+ right: 32rpx;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.share-channels {
|
|
|
+ padding: 16rpx 32rpx 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: transform 0.15s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-item:active {
|
|
|
+ transform: scale(0.92);
|
|
|
+}
|
|
|
+
|
|
|
+.mp-share-btn {
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ line-height: 1;
|
|
|
+ font-size: inherit;
|
|
|
+}
|
|
|
+
|
|
|
+.mp-share-btn::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-icon {
|
|
|
+ width: 100rpx;
|
|
|
+ height: 100rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-icon.wechat {
|
|
|
+ background: linear-gradient(135deg, #07c160, #06ad56);
|
|
|
+}
|
|
|
+
|
|
|
+.channel-icon.timeline {
|
|
|
+ background: linear-gradient(135deg, #1aad19, #07c160);
|
|
|
+}
|
|
|
+
|
|
|
+.channel-icon.qq {
|
|
|
+ background: linear-gradient(135deg, #12b7f5, #0d8bdb);
|
|
|
+}
|
|
|
+
|
|
|
+.channel-icon.poster {
|
|
|
+ background: linear-gradient(135deg, #f093fb, #f5576c);
|
|
|
+}
|
|
|
+
|
|
|
+.icon-emoji {
|
|
|
+ font-size: 48rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.channel-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #4b5563;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.3;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-footer {
|
|
|
+ padding: 16rpx 32rpx 48rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.footer-tip {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+/* H5 端宽度适配 */
|
|
|
+.h5-panel {
|
|
|
+ max-width: 420px;
|
|
|
+ margin: 0 auto;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeIn {
|
|
|
+ from { opacity: 0; }
|
|
|
+ to { opacity: 1; }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideUp {
|
|
|
+ from { transform: translateY(100%); }
|
|
|
+ to { transform: translateY(0); }
|
|
|
+}
|
|
|
+</style>
|