|
@@ -0,0 +1,280 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <TaskDetailSummary :task-detail="taskDetail" />
|
|
|
|
|
+
|
|
|
|
|
+ <view class="share-detail-card">
|
|
|
|
|
+ <view class="share-detail-card__heading">
|
|
|
|
|
+ <view class="share-detail-card__heading-mark" />
|
|
|
|
|
+ <text>分享内容</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="share-detail-info">
|
|
|
|
|
+ <view class="share-detail-info__row">
|
|
|
|
|
+ <text class="share-detail-info__label">转发时间</text>
|
|
|
|
|
+ <text class="share-detail-info__value">{{ forwardTime }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="share-detail-info__row">
|
|
|
|
|
+ <text class="share-detail-info__label">文章标题</text>
|
|
|
|
|
+ <text class="share-detail-info__value">{{ shareTitle }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="share-detail-info__row">
|
|
|
|
|
+ <text class="share-detail-info__label">获得积分</text>
|
|
|
|
|
+ <text class="share-detail-info__value share-detail-info__value--score">
|
|
|
|
|
+ +{{ scoreText }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="share-detail-info__row">
|
|
|
|
|
+ <text class="share-detail-info__label">备注</text>
|
|
|
|
|
+ <text class="share-detail-info__value">{{ remarkText }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <image
|
|
|
|
|
+ v-if="shareImageUrl"
|
|
|
|
|
+ class="share-detail-poster"
|
|
|
|
|
+ :src="shareImageUrl"
|
|
|
|
|
+ mode="widthFix"
|
|
|
|
|
+ @click="previewImages([shareImageUrl])"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <view class="share-detail-qrcode">
|
|
|
|
|
+ <view class="share-detail-qrcode__box">
|
|
|
|
|
+ <view v-if="qrLoading" class="share-detail-qrcode__state">
|
|
|
|
|
+ <wd-loading color="#3b9bed" />
|
|
|
|
|
+ <text>二维码生成中</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <image
|
|
|
|
|
+ v-if="qrCodeUrl"
|
|
|
|
|
+ class="share-detail-qrcode__image"
|
|
|
|
|
+ :src="qrCodeUrl"
|
|
|
|
|
+ mode="aspectFit"
|
|
|
|
|
+ @load="onQrImgLoad"
|
|
|
|
|
+ @error="onQrImgError"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="qrError" class="share-detail-qrcode__state">
|
|
|
|
|
+ <text>{{ qrError }}</text>
|
|
|
|
|
+ <button class="share-detail-qrcode__retry" @click="loadQrCode">重新生成</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
+
|
|
|
|
|
+import { getQrCodeByUrlApi } from '@/services/modules/task/taskDetail'
|
|
|
|
|
+import type { ShareTaskDetailResponse } from '@/services/modules/task/taskDetail/type'
|
|
|
|
|
+
|
|
|
|
|
+import { getAppBaseUrl, normalizeText, previewImages, toFullUrl } from '../utils'
|
|
|
|
|
+import TaskDetailSummary from './TaskDetailSummary.vue'
|
|
|
|
|
+
|
|
|
|
|
+const ARTICLE_TASK_TYPES = new Set(['9', '10'])
|
|
|
|
|
+
|
|
|
|
|
+const props = defineProps<{
|
|
|
|
|
+ taskDetail: ShareTaskDetailResponse
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const qrCodeUrl = ref('')
|
|
|
|
|
+const qrError = ref('')
|
|
|
|
|
+const qrLoading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const forwardTime = computed(() => normalizeText(props.taskDetail.taskInfo.createTime))
|
|
|
|
|
+const shareTitle = computed(() => normalizeText(props.taskDetail.title))
|
|
|
|
|
+const scoreText = computed(() => normalizeText(props.taskDetail.score, '0'))
|
|
|
|
|
+const remarkText = computed(() => normalizeText(props.taskDetail.remark))
|
|
|
|
|
+const shareImageUrl = computed(() => {
|
|
|
|
|
+ const url = props.taskDetail.taskInfo.shareImgUrl
|
|
|
|
|
+ return url ? toFullUrl(url) : ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const buildShareUrl = () => {
|
|
|
|
|
+ const shareId = props.taskDetail.shareId
|
|
|
|
|
+ if (shareId === undefined || shareId === null || shareId === '') return ''
|
|
|
|
|
+
|
|
|
|
|
+ const taskType = String(props.taskDetail.taskType)
|
|
|
|
|
+ const shareType = ARTICLE_TASK_TYPES.has(taskType) ? 'article' : 'null'
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ `${getAppBaseUrl()}/h5/#/pages/artile/detail` +
|
|
|
|
|
+ `?type=${encodeURIComponent(shareType)}` +
|
|
|
|
|
+ `&id=${encodeURIComponent(String(shareId))}`
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadQrCode = async () => {
|
|
|
|
|
+ if (qrLoading.value) return
|
|
|
|
|
+
|
|
|
|
|
+ const shareUrl = buildShareUrl()
|
|
|
|
|
+ qrCodeUrl.value = ''
|
|
|
|
|
+ qrError.value = ''
|
|
|
|
|
+
|
|
|
|
|
+ if (!shareUrl) {
|
|
|
|
|
+ qrError.value = '缺少分享内容 ID,无法生成二维码'
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ qrLoading.value = true
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getQrCodeByUrlApi(shareUrl)
|
|
|
|
|
+ const result = res.data
|
|
|
|
|
+ const imagePath = result?.data?.url
|
|
|
|
|
+
|
|
|
|
|
+ if (!result?.success || result.code !== 0 || !imagePath) {
|
|
|
|
|
+ throw new Error(result?.msg || '二维码接口未返回图片地址')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fullUrl = toFullUrl(imagePath)
|
|
|
|
|
+ qrCodeUrl.value = `${fullUrl}${fullUrl.includes('?') ? '&' : '?'}t=${Date.now()}`
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('[task-detail] 分享二维码生成失败', error)
|
|
|
|
|
+ qrLoading.value = false
|
|
|
|
|
+ qrError.value = error instanceof Error ? error.message : '二维码生成失败'
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onQrImgLoad = () => {
|
|
|
|
|
+ qrLoading.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onQrImgError = () => {
|
|
|
|
|
+ qrLoading.value = false
|
|
|
|
|
+ qrCodeUrl.value = ''
|
|
|
|
|
+ qrError.value = '二维码图片加载失败'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ void loadQrCode()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.share-detail-card {
|
|
|
|
|
+ margin-top: 24rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ box-shadow: 0 8rpx 24rpx rgba(24, 39, 75, 0.05);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-card__heading {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12rpx;
|
|
|
|
|
+ min-height: 86rpx;
|
|
|
|
|
+ padding: 0 28rpx;
|
|
|
|
|
+ color: #1e293b;
|
|
|
|
|
+ font-size: 29rpx;
|
|
|
|
|
+ font-weight: 650;
|
|
|
|
|
+ border-bottom: 1rpx solid #eef2f6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-card__heading-mark {
|
|
|
|
|
+ width: 7rpx;
|
|
|
|
|
+ height: 30rpx;
|
|
|
|
|
+ background: #3b9bed;
|
|
|
|
|
+ border-radius: 4rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-info {
|
|
|
|
|
+ padding: 18rpx 28rpx 6rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-info__row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ padding: 12rpx 0;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ line-height: 40rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-info__label {
|
|
|
|
|
+ flex: 0 0 150rpx;
|
|
|
|
|
+ color: #53647a;
|
|
|
|
|
+ font-size: 27rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-info__value {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ color: #1e293b;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-info__value--score {
|
|
|
|
|
+ color: #f59e0b;
|
|
|
|
|
+ font-weight: 650;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-poster {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: calc(100% - 56rpx);
|
|
|
|
|
+ margin: 18rpx 28rpx 0;
|
|
|
|
|
+ background: #eef2f6;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 24rpx 28rpx 34rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode__box {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 340rpx;
|
|
|
|
|
+ height: 340rpx;
|
|
|
|
|
+ padding: 12rpx;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border: 1rpx solid #e8edf3;
|
|
|
|
|
+ border-radius: 18rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode__image {
|
|
|
|
|
+ width: 316rpx;
|
|
|
|
|
+ height: 316rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode__state {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ z-index: 1;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 18rpx;
|
|
|
|
|
+ padding: 28rpx;
|
|
|
|
|
+ color: #8491a2;
|
|
|
|
|
+ font-size: 23rpx;
|
|
|
|
|
+ line-height: 34rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border-radius: 18rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode__retry {
|
|
|
|
|
+ height: 58rpx;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0 28rpx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 23rpx;
|
|
|
|
|
+ line-height: 58rpx;
|
|
|
|
|
+ background: #3b9bed;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 29rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.share-detail-qrcode__retry::after {
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|