222 lines
6.0 KiB
Vue
222 lines
6.0 KiB
Vue
<template>
|
||
<view class="share-report-btns">
|
||
<!-- 分享按钮 -->
|
||
<view class="action-btn share-btn" @tap="handleShare">
|
||
<image class="btn-icon" src="/static/assetDetail/fenxiang.png" mode="aspectFit"></image>
|
||
<text class="btn-text">分享</text>
|
||
</view>
|
||
<!-- 举报按钮 - 只在不是自己的资产时显示 -->
|
||
<view v-if="showReport" class="action-btn report-btn" @tap="handleReport">
|
||
<image class="btn-icon" src="/static/assetDetail/jubao.png" mode="aspectFit"></image>
|
||
<text class="btn-text">举报</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分享弹窗 -->
|
||
<ShareModal
|
||
:visible="showShareModal"
|
||
:coverUrl="shareCoverUrl"
|
||
:qrcodeUrl="shareQrcodeUrl"
|
||
:avatarUrl="shareAvatarUrl"
|
||
:nickname="ownerNickname"
|
||
:assetId="assetId"
|
||
:displayTxHash="displayTxHash"
|
||
:externalCanvasId="externalCanvasId"
|
||
@close="showShareModal = false"
|
||
/>
|
||
|
||
<!-- 举报弹窗 -->
|
||
<ReportModal
|
||
:visible="showReportModal"
|
||
:assetId="assetId"
|
||
@close="showReportModal = false"
|
||
@submit="handleReportSubmit"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
import ShareModal from './ShareModal.vue';
|
||
import ReportModal from './ReportModal.vue';
|
||
import { getShareQrcodeApi } from '@/utils/api.js';
|
||
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
||
|
||
const props = defineProps({
|
||
// 资产拥有者的昵称
|
||
ownerNickname: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 资产ID,用于分享
|
||
assetId: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 封面图片(已解析的 CDN URL)
|
||
coverUrl: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 封面原始 OSS key(异步 coverUrl 还没解析完时,ShareReportButtons 自己 resolve)
|
||
coverKey: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 外部 page 级 canvasId(app-plus 组件内 canvas 不稳,推荐父页面提供)
|
||
externalCanvasId: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 链上哈希(原文,由父组件 asset-detail 提供;image-compositor 内部截断渲染)
|
||
displayTxHash: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
});
|
||
|
||
// 当前用户昵称
|
||
const currentNickname = computed(() => {
|
||
try {
|
||
const userStr = uni.getStorageSync('user');
|
||
if (userStr) {
|
||
const userInfo = JSON.parse(userStr);
|
||
return userInfo?.nickname || '';
|
||
}
|
||
} catch (e) {
|
||
console.error('获取用户信息失败:', e);
|
||
}
|
||
return '';
|
||
});
|
||
|
||
// 是否显示举报按钮(不是自己的资产时显示)
|
||
const showReport = computed(() => {
|
||
return props.ownerNickname && currentNickname.value && props.ownerNickname !== currentNickname.value;
|
||
});
|
||
|
||
// 分享弹窗状态
|
||
const showShareModal = ref(false);
|
||
const shareCoverUrl = ref('');
|
||
const shareQrcodeUrl = ref('');
|
||
const shareAvatarUrl = ref('');
|
||
|
||
// 举报弹窗状态
|
||
const showReportModal = ref(false);
|
||
|
||
// 分享
|
||
const handleShare = async () => {
|
||
// 登录门槛:未登录不打开弹窗(spec § 3.4)
|
||
const userStr = uni.getStorageSync('user');
|
||
if (!userStr || !JSON.parse(userStr)?.uid) {
|
||
uni.showToast({ title: '请先登录', icon: 'none' });
|
||
setTimeout(() => uni.navigateTo({ url: '/pages/login/login' }), 800);
|
||
return;
|
||
}
|
||
|
||
// 关键:coverUrl 是异步加载的(props.coverUrl 可能还没解析完),如果空就用 coverKey 自己 resolve
|
||
// 优先用 props.coverKey(原始 OSS key) + getAssetCoverRealUrl 拼接真实 URL,
|
||
// 因为 coverUrl 可能只缓存了短期签名 URL,share 流程需要长期可用的 URL
|
||
const sourceKey = props.coverKey || props.coverUrl;
|
||
let cover = '';
|
||
console.log('[ShareReportButtons] handleShare coverUrl =', props.coverUrl, 'coverKey =', props.coverKey);
|
||
try {
|
||
cover = await getAssetCoverRealUrl(sourceKey);
|
||
console.log('[ShareReportButtons] resolved cover =', cover);
|
||
} catch (e) {
|
||
console.warn('[ShareReportButtons] resolve cover failed, fallback raw:', e);
|
||
// 兜底:用原始值
|
||
cover = props.coverUrl || props.coverKey;
|
||
}
|
||
if (!cover) {
|
||
uni.showToast({ title: '封面加载中,请稍后再试', icon: 'none' });
|
||
return;
|
||
}
|
||
shareCoverUrl.value = cover;
|
||
|
||
// 分享者头像:从本地存储读取当前登录用户的头像
|
||
try {
|
||
const userStr2 = uni.getStorageSync('user');
|
||
if (userStr2) {
|
||
const userInfo = JSON.parse(userStr2);
|
||
shareAvatarUrl.value = userInfo?.avatar_url || '/static/square/gerenzhongxinkangpinkuang.png';
|
||
} else {
|
||
shareAvatarUrl.value = '/static/square/gerenzhongxinkangpinkuang.png';
|
||
}
|
||
} catch (e) {
|
||
console.error('获取用户头像失败:', e);
|
||
shareAvatarUrl.value = '/static/square/gerenzhongxinkangpinkuang.png';
|
||
}
|
||
// 先用 mock 占位,后端真实接口返回后会替换
|
||
shareQrcodeUrl.value = '/static/share/mock_qrcode.png';
|
||
showShareModal.value = true;
|
||
refreshQrcode();
|
||
};
|
||
|
||
// 在 showShareModal = true 后(或 watch 回调里),异步拉真实二维码 URL(spec § 3)
|
||
async function refreshQrcode() {
|
||
const userStr = uni.getStorageSync('user');
|
||
if (!userStr) return;
|
||
const user = JSON.parse(userStr);
|
||
try {
|
||
// 用异步版本(spec § 3.3 推荐 — 不阻塞 UI 线程)
|
||
const { platform } = await uni.getSystemInfo();
|
||
const data = await getShareQrcodeApi(props.assetId, user.uid, platform);
|
||
if (data?.qrcode_url) {
|
||
shareQrcodeUrl.value = data.qrcode_url;
|
||
console.log(data.qrcode_url)
|
||
}
|
||
} catch (e) {
|
||
// 失败保持 mock 占位图,不打断用户
|
||
}
|
||
}
|
||
|
||
// 举报 - 打开举报弹窗
|
||
const handleReport = () => {
|
||
if (!props.assetId) {
|
||
uni.showToast({ title: '藏品信息缺失', icon: 'none' });
|
||
return;
|
||
}
|
||
showReportModal.value = true;
|
||
};
|
||
|
||
// 举报提交完成回调(埋点/埋日志可用)
|
||
const handleReportSubmit = (payload) => {
|
||
console.log('[ShareReportButtons] report submitted', payload);
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.share-report-btns {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 24rpx;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.action-btn:active {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.btn-icon {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.5));
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 24rpx;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.6);
|
||
}
|
||
</style> |