Files
sbnews/uniapp/src/components/share-popup/share-popup.vue
T
2026-06-11 12:15:29 +08:00

386 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<u-popup v-model="innerShow" mode="center" round="16" @close="emit('update:show', false)">
<view class="share-popup">
<view class="share-popup__header">
<text class="share-popup__title">分享给朋友</text>
<view class="share-popup__close" @tap="emit('update:show', false)">
<u-icon name="close" size="20" />
</view>
</view>
<view v-if="loading" class="share-popup__placeholder">
<text class="share-popup__placeholder-text">生成中...</text>
</view>
<view v-else-if="posterUrl" class="share-popup__poster">
<image :src="posterUrl" class="share-popup__poster-img" mode="widthFix" />
</view>
<view v-else-if="qrcodeUrl" class="share-popup__card">
<view class="share-popup__card-header">
<text class="share-popup__card-appname">{{ posterTitle }}</text>
<text v-if="sharerNickname" class="share-popup__card-sharer">分享自 {{ sharerNickname }}</text>
</view>
<view class="share-popup__card-qr-wrap">
<image :src="qrcodeUrl" class="share-popup__card-qr" mode="widthFix" />
</view>
<text v-if="posterPageTitle" class="share-popup__card-title">{{ posterPageTitle }}</text>
</view>
<view v-else class="share-popup__placeholder">
<text class="share-popup__placeholder-text">暂无图片</text>
</view>
<view class="share-popup__code" v-if="shareCode">
<text class="share-popup__code-label">分享码</text>
<text class="share-popup__code-value">{{ shareCode }}</text>
<text class="share-popup__code-copy" @tap="copyCode">复制</text>
</view>
<view class="share-popup__actions">
<view class="share-popup__btn share-popup__btn--save" @tap="saveImage">
<u-icon name="download" size="18" color="#fff" />
<text>保存图片</text>
</view>
<view class="share-popup__btn share-popup__btn--copy" @tap="copyLink">
<u-icon name="link" size="18" color="#fff" />
<text>复制链接</text>
</view>
</view>
</view>
</u-popup>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { getShareInfo, createShortLink } from '@/api/share'
const props = defineProps<{
show: boolean
/** 页面类型: match | news | post | crypto | invite ... */
pageType: string
/** 页面ID(可选,invite等类型无需传) */
pageId?: number | string
/** 目标页面路径(可选,如 /pages/index/index */
path?: string
/** 页面标题 */
title: string
/** 页面描述(可选) */
description?: string
}>()
const emit = defineEmits(['update:show'])
const innerShow = ref(false)
const shortUrl = ref('')
const posterUrl = ref('')
const qrcodeUrl = ref('')
const shareCode = ref('')
const posterTitle = ref('')
const posterPageTitle = ref('')
const sharerNickname = ref('')
const loading = ref(false)
const init = async () => {
loading.value = true
posterUrl.value = ''
qrcodeUrl.value = ''
shortUrl.value = ''
shareCode.value = ''
posterTitle.value = ''
posterPageTitle.value = ''
sharerNickname.value = ''
try {
const info = await getShareInfo()
const data: any = {
page_type: props.pageType,
invite_code: info.invite_code || '',
path: props.path || '',
title: props.title || '',
description: props.description || ''
}
if (props.pageId) {
data.page_id = props.pageId
}
const res: any = await createShortLink(data)
shortUrl.value = res.short_url || ''
shareCode.value = res.code || ''
posterUrl.value = res.poster_url || (res.poster && res.poster.poster_url) || ''
qrcodeUrl.value = res.qrcode_url || ''
const poster = res.poster || {}
posterTitle.value = poster.poster_title || ''
posterPageTitle.value = poster.title || props.title || ''
sharerNickname.value = (poster.sharer && poster.sharer.nickname) || ''
} catch (e) {
console.error('生成分享图失败', e)
uni.showToast({ title: '生成失败,请重试', icon: 'none' })
} finally {
loading.value = false
}
}
watch(
() => props.show,
(val: boolean) => {
innerShow.value = val
if (val) {
init()
}
},
{ immediate: true }
)
watch(innerShow, (val: boolean) => {
if (!val && props.show) {
emit('update:show', false)
}
})
const saveImage = () => {
const targetUrl = posterUrl.value || qrcodeUrl.value
if (!targetUrl) {
uni.showToast({ title: '图片生成中...', icon: 'none' })
return
}
// #ifdef H5
const a = document.createElement('a')
a.href = targetUrl
a.download = `share_${props.pageType}_${props.pageId || ''}.png`
a.target = '_blank'
a.click()
uni.showToast({ title: '已下载', icon: 'success' })
// #endif
// #ifndef H5
uni.showLoading({ title: '保存中...' })
uni.downloadFile({
url: targetUrl,
success: (res) => {
if (res.statusCode !== 200) {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
return
}
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.hideLoading()
uni.showToast({ title: '已保存到相册', icon: 'success' })
},
fail: (err) => {
uni.hideLoading()
if (err.errMsg && err.errMsg.includes('auth')) {
uni.showModal({
title: '提示',
content: '需要相册权限才能保存图片,请在系统设置中开启',
showCancel: false
})
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
})
},
fail: () => {
uni.hideLoading()
uni.showToast({ title: '下载失败', icon: 'none' })
}
})
// #endif
}
const copyLink = () => {
if (!shortUrl.value) {
uni.showToast({ title: '链接生成中...', icon: 'none' })
return
}
uni.setClipboardData({
data: shortUrl.value,
success: () => {
uni.showToast({ title: '链接已复制', icon: 'success' })
}
})
}
const copyCode = () => {
if (!shareCode.value) {
uni.showToast({ title: '分享码生成中...', icon: 'none' })
return
}
uni.setClipboardData({
data: shareCode.value,
success: () => {
uni.showToast({ title: '分享码已复制', icon: 'success' })
}
})
}
</script>
<style lang="scss" scoped>
.share-popup {
width: 600rpx;
padding: 40rpx;
background: #fff;
border-radius: 24rpx;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
&__title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
&__close {
padding: 10rpx;
}
&__placeholder {
min-height: 400rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f6f7f9;
border-radius: 16rpx;
margin-bottom: 30rpx;
}
&__placeholder-text {
font-size: 26rpx;
color: #999;
}
&__poster {
width: 100%;
border-radius: 16rpx;
overflow: hidden;
background: #000;
margin-bottom: 30rpx;
}
&__poster-img {
width: 100%;
display: block;
}
&__card {
width: 100%;
border-radius: 16rpx;
overflow: hidden;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin-bottom: 30rpx;
padding: 40rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
}
&__card-header {
text-align: center;
margin-bottom: 30rpx;
}
&__card-appname {
font-size: 32rpx;
font-weight: 600;
color: #fff;
display: block;
}
&__card-sharer {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
margin-top: 8rpx;
display: block;
}
&__card-qr-wrap {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 24rpx;
}
&__card-qr {
width: 280rpx;
height: 280rpx;
display: block;
}
&__card-title {
font-size: 26rpx;
color: #fff;
text-align: center;
line-height: 1.5;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&__code {
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 20rpx;
background: #f6f7f9;
border-radius: 12rpx;
margin-bottom: 10rpx;
gap: 8rpx;
}
&__code-label {
font-size: 24rpx;
color: #999;
}
&__code-value {
font-size: 26rpx;
color: #333;
font-weight: 500;
letter-spacing: 2rpx;
}
&__code-copy {
font-size: 22rpx;
color: #667eea;
padding: 4rpx 12rpx;
border: 1rpx solid #667eea;
border-radius: 8rpx;
margin-left: 8rpx;
}
&__actions {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
}
&__btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
color: #fff;
&--save {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
&--copy {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
}
}
</style>