Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
飞
飞鹤小程序
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
FH
飞鹤小程序
Commits
355d62f2
Commit
355d62f2
authored
Nov 12, 2025
by
spc
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'xinmalabSharePoster' into dev
parents
f436a82f
e6cb8b18
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
995 additions
and
21 deletions
+995
-21
xingmaLab.js
api/xingmaLab.js
+3
-1
XingmaLabPosterPage.vue
components/xingmaLab/XingmaLabPosterPage.vue
+705
-0
XingmaLabSharePopup.vue
components/xingmaLab/XingmaLabSharePopup.vue
+206
-0
XingmaLabDetailPage.vue
pages/XingmaLabDetailPage/XingmaLabDetailPage.vue
+81
-20
No files found.
api/xingmaLab.js
View file @
355d62f2
import
requestModule
from
'./request.js'
const
{
api
}
=
requestModule
const
{
api
}
=
requestModule
/**
* 星妈会藏馆
...
...
@@ -38,3 +38,5 @@ export const fetchRecordDetail = (data) => api.get('/c/lab/record/detail', data)
export
const
fetchFavoriteAdd
=
(
data
)
=>
api
.
get
(
'/c/lab/favorite/add'
,
data
)
export
const
fetchFavoriteRemove
=
(
data
)
=>
api
.
get
(
'/c/lab/favorite/remove'
,
data
)
export
const
generateQRCode
=
(
data
)
=>
api
.
post
(
'/c/wechat/generateQRCode'
,
data
)
\ No newline at end of file
components/xingmaLab/XingmaLabPosterPage.vue
0 → 100644
View file @
355d62f2
<
template
>
<view
v-if=
"visible"
class=
"poster-page-overlay"
@
tap=
"handleOverlayClick"
>
<view
class=
"poster-page-container"
@
tap
.
stop
>
<!-- 海报背景 -->
<view
class=
"poster-wrapper"
>
<image
class=
"poster-bg"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBg.png`" mode="aspectFit">
</image>
<!-- 昵称区域 -->
<view
class=
"poster-nickname-wrapper"
>
<!-- 昵称背景 -->
<image
class=
"poster-nickname-bg"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterNickBg2.png`"
mode="aspectFit">
</image>
<!-- 头像 -->
<image
v-if=
"avatar"
class=
"poster-avatar"
:src=
"avatar"
mode=
"aspectFit"
></image>
<text
class=
"poster-nickname-text"
>
{{
nickname
||
''
}}
</text>
</view>
<!-- 图片区域 -->
<view
class=
"poster-image-wrapper"
>
<image
class=
"poster-image-bg"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterImgBg.png`"
mode="aspectFit">
</image>
<image
v-if=
"imageUrl"
class=
"poster-image"
:src=
"imageUrl"
mode=
"aspectFit"
></image>
<!-- 文字内容 -->
<view
class=
"poster-content"
>
<text
class=
"poster-content-text"
>
{{
content
||
''
}}
</text>
</view>
<!-- 唯一藏品编号 -->
<view
class=
"poster-collection-number"
>
<text
class=
"collection-number-text"
>
唯一藏品编号:
{{
collectionNumber
}}
</text>
</view>
<!-- 下载按钮 -->
<view
class=
"poster-download-btn"
@
tap=
"handleDownload"
>
<image
class=
"download-icon"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterDownloadBtn.png`"
mode="aspectFit">
</image>
</view>
<!-- 二维码区域 -->
<view
class=
"poster-qrcode-wrapper"
>
<image
v-if=
"qrcodeImageUrl"
class=
"poster-qrcode-image"
:src=
"qrcodeImageUrl"
mode=
"aspectFit"
>
</image>
</view>
</view>
</view>
<!-- 关闭按钮 -->
<view
class=
"poster-close-btn"
@
tap=
"handleClose"
>
<image
class=
"close-icon"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBtnConCloseBtn2.png`"
mode="aspectFit">
</image>
</view>
</view>
<!-- 隐藏的 canvas 用于合成海报 -->
<canvas
canvas-id=
"posterCanvas"
class=
"poster-canvas"
:style=
"
{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
</canvas>
</view>
</
template
>
<
script
setup
>
import
{
ref
,
watch
,
getCurrentInstance
,
nextTick
,
computed
}
from
'vue'
import
md
from
'../../md'
import
{
generateQRCode
}
from
'../../api/xingmaLab'
const
{
proxy
}
=
getCurrentInstance
()
const
$baseUrl
=
proxy
.
$baseUrl
const
props
=
defineProps
({
visible
:
{
type
:
Boolean
,
default
:
false
},
imageUrl
:
{
type
:
String
,
default
:
''
},
content
:
{
type
:
String
,
default
:
'可爱'
},
collectionNumber
:
{
type
:
String
,
default
:
''
},
defaultNickname
:
{
type
:
String
,
default
:
''
},
avatar
:
{
type
:
String
,
default
:
''
},
recordId
:
{
type
:
String
,
default
:
''
}
})
const
emit
=
defineEmits
([
'close'
,
'download'
])
// 昵称从 props 传入,不可编辑
const
nickname
=
computed
(()
=>
props
.
defaultNickname
||
''
)
const
qrcodeImageUrl
=
ref
(
''
)
// 二维码图片 URL
const
isDownloading
=
ref
(
false
)
// 是否正在下载
// Canvas 尺寸(海报尺寸:542rpx x 956rpx)
const
systemInfo
=
uni
.
getSystemInfoSync
()
const
screenWidth
=
systemInfo
.
windowWidth
||
375
const
canvasWidth
=
(
542
/
750
)
*
screenWidth
const
canvasHeight
=
(
956
/
750
)
*
screenWidth
// 监听 visible 变化,生成二维码
watch
(()
=>
props
.
visible
,
(
newVal
)
=>
{
if
(
newVal
)
{
nextTick
(()
=>
{
generateQrcodeFunc
()
})
}
})
// 生成二维码
const
generateQrcodeFunc
=
async
()
=>
{
if
(
!
props
.
recordId
)
{
console
.
warn
(
'recordId 未传入,无法生成二维码'
)
return
}
try
{
const
result
=
await
generateQRCode
({
scene
:
`id=
${
props
.
recordId
}
`
,
page
:
`pages/XingmaLabDetailPage/XingmaLabDetailPage`
,
envVersion
:
'develop'
})
if
(
result
&&
result
.
ok
&&
result
.
data
&&
result
.
data
.
qrCodeBase64
)
{
// 接口返回的二维码图片 Base64 编码
const
qrCodeBase64
=
result
.
data
.
qrCodeBase64
// 判断是否为 JPEG 格式(JPEG Base64 通常以 /9j/ 开头)
const
isJPEG
=
qrCodeBase64
.
startsWith
(
'/9j/'
)
// 移除可能存在的 data:image 前缀,只保留纯 Base64 字符串
let
base64String
=
qrCodeBase64
if
(
base64String
.
includes
(
','
))
{
base64String
=
base64String
.
split
(
','
)[
1
]
}
// 将 Base64 转换为 Data URI
const
base64Data
=
isJPEG
?
`data:image/jpeg;base64,
${
base64String
}
`
:
`data:image/png;base64,
${
base64String
}
`
// 直接赋值给图片 URL
qrcodeImageUrl
.
value
=
base64Data
}
else
{
console
.
warn
(
'生成二维码接口返回数据格式异常:'
,
result
)
}
}
catch
(
error
)
{
console
.
error
(
'生成二维码失败:'
,
error
)
}
}
// 关闭弹窗
const
handleClose
=
()
=>
{
emit
(
'close'
)
}
// 点击遮罩关闭
const
handleOverlayClick
=
()
=>
{
handleClose
()
}
// 下载海报
const
handleDownload
=
async
()
=>
{
if
(
isDownloading
.
value
)
{
return
}
// 检查二维码是否已生成,如果没有则等待生成
if
(
!
qrcodeImageUrl
.
value
)
{
uni
.
showLoading
({
title
:
'二维码生成中...'
,
mask
:
true
})
// 等待二维码生成,最多等待 5 秒
let
waitCount
=
0
while
(
!
qrcodeImageUrl
.
value
&&
waitCount
<
50
)
{
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
100
))
waitCount
++
}
uni
.
hideLoading
()
if
(
!
qrcodeImageUrl
.
value
)
{
uni
.
showToast
({
title
:
'二维码生成失败,请重试'
,
icon
:
'none'
})
return
}
}
isDownloading
.
value
=
true
uni
.
showLoading
({
title
:
'生成中...'
,
mask
:
true
})
try
{
// 等待所有图片加载完成
await
nextTick
()
// 额外等待一下,确保二维码图片已加载
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
500
))
const
ctx
=
uni
.
createCanvasContext
(
'posterCanvas'
,
proxy
)
// 保存初始状态
ctx
.
save
()
// 海报尺寸(rpx 转 px)
const
posterWidth
=
canvasWidth
const
posterHeight
=
canvasHeight
// 绘制背景
await
drawImage
(
ctx
,
`
${
$baseUrl
}
homepage/Q3Res/xingmaLabPosterBg.png`
,
0
,
0
,
posterWidth
,
posterHeight
)
// 绘制昵称背景和头像、昵称
const
nicknameBgX
=
(
24
/
750
)
*
screenWidth
const
nicknameBgY
=
(
40
/
750
)
*
screenWidth
const
nicknameBgW
=
(
256
/
750
)
*
screenWidth
const
nicknameBgH
=
(
58
/
750
)
*
screenWidth
await
drawImage
(
ctx
,
`
${
$baseUrl
}
homepage/Q3Res/xingmaLabPosterNickBg2.png`
,
nicknameBgX
,
nicknameBgY
,
nicknameBgW
,
nicknameBgH
)
// 绘制头像(带圆角)
if
(
props
.
avatar
)
{
// 头像位置:相对于昵称背景,top: 2rpx, left: 4rpx
const
avatarX
=
nicknameBgX
+
(
8
/
750
)
*
screenWidth
const
avatarY
=
nicknameBgY
+
(
6
/
750
)
*
screenWidth
const
avatarSize
=
(
48
/
750
)
*
screenWidth
const
avatarRadius
=
avatarSize
/
2
// 保存当前状态
ctx
.
save
()
// 创建圆形裁剪路径
ctx
.
beginPath
()
ctx
.
arc
(
avatarX
+
avatarRadius
,
avatarY
+
avatarRadius
,
avatarRadius
,
0
,
2
*
Math
.
PI
)
ctx
.
clip
()
// 绘制头像图片
await
drawImage
(
ctx
,
props
.
avatar
,
avatarX
,
avatarY
,
avatarSize
,
avatarSize
)
// 恢复裁剪区域
ctx
.
restore
()
}
// 绘制昵称文字
ctx
.
setFillStyle
(
'#333333'
)
ctx
.
setFontSize
((
28
/
750
)
*
screenWidth
)
ctx
.
setTextAlign
(
'left'
)
const
nicknameTextX
=
nicknameBgX
+
(
props
.
avatar
?
(
48
+
20
)
/
750
*
screenWidth
:
30
/
750
*
screenWidth
)
const
nicknameTextY
=
nicknameBgY
+
nicknameBgH
/
2
+
(
28
/
750
)
*
screenWidth
/
3
ctx
.
fillText
(
nickname
.
value
||
''
,
nicknameTextX
,
nicknameTextY
)
// 绘制图片区域背景(居中)
const
imageBgW
=
(
494
/
750
)
*
screenWidth
const
imageBgH
=
(
802
/
750
)
*
screenWidth
const
imageBgX
=
(
posterWidth
-
imageBgW
)
/
2
const
imageBgY
=
(
120
/
750
)
*
screenWidth
await
drawImage
(
ctx
,
`
${
$baseUrl
}
homepage/Q3Res/xingmaLabPosterImgBg.png`
,
imageBgX
,
imageBgY
,
imageBgW
,
imageBgH
)
// 绘制主图片
if
(
props
.
imageUrl
)
{
const
imageX
=
imageBgX
+
(
26
/
750
)
*
screenWidth
const
imageY
=
imageBgY
+
(
26
/
750
)
*
screenWidth
const
imageW
=
imageBgW
-
(
52
/
750
)
*
screenWidth
const
imageH
=
(
586
/
750
)
*
screenWidth
await
drawImage
(
ctx
,
props
.
imageUrl
,
imageX
,
imageY
,
imageW
,
imageH
)
}
// 绘制文字内容
ctx
.
setFillStyle
(
'#000000'
)
ctx
.
setFontSize
((
28
/
750
)
*
screenWidth
)
ctx
.
setTextAlign
(
'left'
)
const
contentX
=
imageBgX
+
(
22
/
750
)
*
screenWidth
const
contentY
=
imageBgY
+
imageBgH
-
(
146
/
750
)
*
screenWidth
ctx
.
fillText
(
props
.
content
||
''
,
contentX
,
contentY
)
// 绘制唯一藏品编号
ctx
.
setFillStyle
(
'#D3A458'
)
ctx
.
setFontSize
((
20
/
750
)
*
screenWidth
)
ctx
.
setTextAlign
(
'left'
)
const
collectionX
=
imageBgX
+
(
22
/
750
)
*
screenWidth
const
collectionY
=
imageBgY
+
imageBgH
-
(
116
/
750
)
*
screenWidth
ctx
.
fillText
(
`唯一藏品编号:
${
props
.
collectionNumber
||
''
}
`
,
collectionX
,
collectionY
)
// 绘制二维码(在图片区域右下角)
if
(
qrcodeImageUrl
.
value
)
{
const
qrcodeSize
=
(
114
/
750
)
*
screenWidth
// 二维码位置:图片区域右下角,距离右边 22rpx,距离底部 26rpx
const
qrcodeX
=
imageBgX
+
imageBgW
-
qrcodeSize
-
(
22
/
750
)
*
screenWidth
const
qrcodeY
=
imageBgY
+
imageBgH
-
qrcodeSize
-
(
26
/
750
)
*
screenWidth
console
.
log
(
'开始绘制二维码:'
,
{
qrcodeX
,
qrcodeY
,
qrcodeSize
,
imageBgX
,
imageBgY
,
imageBgW
,
imageBgH
,
qrcodeImageUrl
:
qrcodeImageUrl
.
value
.
substring
(
0
,
50
)
+
'...'
})
await
drawImage
(
ctx
,
qrcodeImageUrl
.
value
,
qrcodeX
,
qrcodeY
,
qrcodeSize
,
qrcodeSize
)
console
.
log
(
'二维码绘制完成'
)
}
else
{
console
.
warn
(
'二维码图片 URL 为空,无法绘制'
)
}
// 等待所有绘制完成后再调用 draw
await
nextTick
()
// 绘制到 canvas
ctx
.
draw
(
false
,
async
()
=>
{
await
nextTick
()
// 转换为临时文件
uni
.
canvasToTempFilePath
({
canvasId
:
'posterCanvas'
,
width
:
posterWidth
,
height
:
posterHeight
,
destWidth
:
posterWidth
,
destHeight
:
posterHeight
,
success
:
(
res
)
=>
{
// 保存到相册
uni
.
saveImageToPhotosAlbum
({
filePath
:
res
.
tempFilePath
,
success
:
()
=>
{
uni
.
hideLoading
()
uni
.
showToast
({
title
:
'保存成功'
,
icon
:
'success'
})
isDownloading
.
value
=
false
},
fail
:
(
err
)
=>
{
uni
.
hideLoading
()
console
.
error
(
'保存失败:'
,
err
)
uni
.
showToast
({
title
:
'保存失败'
,
icon
:
'none'
})
isDownloading
.
value
=
false
}
})
},
fail
:
(
err
)
=>
{
uni
.
hideLoading
()
console
.
error
(
'生成图片失败:'
,
err
)
uni
.
showToast
({
title
:
'生成失败'
,
icon
:
'none'
})
isDownloading
.
value
=
false
}
},
proxy
)
})
}
catch
(
error
)
{
uni
.
hideLoading
()
console
.
error
(
'下载海报失败:'
,
error
)
uni
.
showToast
({
title
:
'生成失败'
,
icon
:
'none'
})
isDownloading
.
value
=
false
}
}
// 将 Base64 Data URI 转换为临时文件路径
const
base64ToTempFilePath
=
(
base64Data
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
base64Data
||
!
base64Data
.
startsWith
(
'data:image'
))
{
reject
(
new
Error
(
'无效的 Base64 数据'
))
return
}
const
fs
=
uni
.
getFileSystemManager
()
const
base64String
=
base64Data
.
split
(
','
)[
1
]
const
isJPEG
=
base64String
.
startsWith
(
'/9j/'
)
const
fileExtension
=
isJPEG
?
'jpg'
:
'png'
const
fileName
=
`qrcode_
${
Date
.
now
()}
_
${
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
)}
.
${
fileExtension
}
`
// #ifdef MP-WEIXIN
const
userDataPath
=
wx
.
env
.
USER_DATA_PATH
// #endif
// #ifndef MP-WEIXIN
const
userDataPath
=
uni
.
env
.
USER_DATA_PATH
||
''
// #endif
const
filePath
=
`
${
userDataPath
}
/
${
fileName
}
`
// 先尝试清理旧的临时文件(异步方式,避免阻塞)
// #ifdef MP-WEIXIN
if
(
userDataPath
)
{
fs
.
readdir
({
dirPath
:
userDataPath
,
success
:
(
res
)
=>
{
const
qrcodeFiles
=
res
.
files
.
filter
(
f
=>
f
.
startsWith
(
'qrcode_'
))
// 只保留最新的 2 个文件,删除其他旧文件
if
(
qrcodeFiles
.
length
>
2
)
{
qrcodeFiles
.
sort
().
slice
(
0
,
qrcodeFiles
.
length
-
2
).
forEach
(
oldFile
=>
{
fs
.
unlink
({
filePath
:
`
${
userDataPath
}
/
${
oldFile
}
`
,
success
:
()
=>
{
},
fail
:
()
=>
{
}
})
})
}
// 清理完成后写入新文件
writeBase64File
()
},
fail
:
()
=>
{
// 如果读取目录失败,直接写入
writeBase64File
()
}
})
}
else
{
writeBase64File
()
}
// #endif
// #ifndef MP-WEIXIN
writeBase64File
()
// #endif
function
writeBase64File
()
{
fs
.
writeFile
({
filePath
:
filePath
,
data
:
base64String
,
encoding
:
'base64'
,
success
:
()
=>
{
resolve
(
filePath
)
},
fail
:
(
err
)
=>
{
console
.
error
(
'Base64 图片写入失败:'
,
err
)
// 如果写入失败,尝试使用临时文件路径(uni.downloadFile 返回的路径)
reject
(
err
)
}
})
}
})
}
// 绘制图片到 canvas(异步加载图片)
const
drawImage
=
(
ctx
,
src
,
x
,
y
,
w
,
h
)
=>
{
return
new
Promise
((
resolve
)
=>
{
// 如果是 Base64 Data URI,先转换为临时文件
if
(
src
.
startsWith
(
'data:image'
))
{
base64ToTempFilePath
(
src
).
then
((
filePath
)
=>
{
ctx
.
drawImage
(
filePath
,
x
,
y
,
w
,
h
)
resolve
()
}).
catch
((
err
)
=>
{
console
.
error
(
'Base64 图片处理失败:'
,
err
)
// 尝试直接使用 Base64(某些平台可能支持)
try
{
ctx
.
drawImage
(
src
,
x
,
y
,
w
,
h
)
resolve
()
}
catch
(
e
)
{
console
.
warn
(
'无法绘制 Base64 图片,跳过'
)
resolve
()
// 即使失败也继续
}
})
}
else
{
// 普通图片 URL,下载后绘制
uni
.
downloadFile
({
url
:
src
,
success
:
(
res
)
=>
{
if
(
res
.
statusCode
===
200
)
{
ctx
.
drawImage
(
res
.
tempFilePath
,
x
,
y
,
w
,
h
)
resolve
()
}
else
{
console
.
warn
(
'图片下载失败:'
,
src
,
'statusCode:'
,
res
.
statusCode
)
resolve
()
// 即使失败也继续
}
},
fail
:
(
err
)
=>
{
console
.
warn
(
'图片下载失败:'
,
src
,
err
)
resolve
()
// 即使失败也继续
}
})
}
})
}
</
script
>
<
style
lang=
"less"
scoped
>
.poster-page-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.poster-page-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.poster-wrapper {
position: relative;
width: 542rpx;
height: 956rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.poster-bg {
width: 542rpx;
height: 956rpx;
z-index: 0;
}
.poster-nickname-wrapper {
position: absolute;
top: 40rpx;
left: 24rpx;
display: flex;
align-items: center;
z-index: 1;
}
.poster-nickname-bg {
position: relative;
width: 256rpx;
height: 58rpx;
z-index: 0;
}
.poster-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
flex-shrink: 0;
z-index: 1;
position: absolute;
top: 6rpx;
left: 8rpx;
}
.poster-nickname-text {
position: absolute;
left: 0;
top: 0;
width: 256rpx;
height: 58rpx;
padding: 0 30rpx;
font-size: 28rpx;
color: #333333;
text-align: center;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.poster-image-wrapper {
position: absolute;
top: 120rpx;
left: 50%;
transform: translateX(-50%);
width: 494rpx;
height: 802rpx;
display: flex;
justify-content: center;
z-index: 1;
}
.poster-image-bg {
position: absolute;
top: 0;
left: 0;
width: 494rpx;
height: 802rpx;
z-index: 0;
}
.poster-image {
position: relative;
height: 586rpx;
z-index: 1;
object-fit: contain;
margin: 26rpx;
box-sizing: border-box;
}
.poster-content {
position: absolute;
bottom: 146rpx;
left: 22rpx;
width: calc(100% - 44rpx);
z-index: 2;
}
.poster-content-text {
font-size: 28rpx;
font-weight: 500;
color: #000000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.poster-collection-number {
position: absolute;
bottom: 116rpx;
left: 22rpx;
width: calc(100% - 44rpx);
text-align: left;
z-index: 2;
}
.collection-number-text {
font-size: 20rpx;
color: #D3A458;
}
.poster-download-btn {
position: absolute;
bottom: 36rpx;
left: 22rpx;
width: 118rpx;
height: 46rpx;
display: flex;
z-index: 2;
}
.download-icon {
width: 100%;
height: 100%;
}
.poster-qrcode-wrapper {
position: absolute;
bottom: 26rpx;
right: 22rpx;
width: 114rpx;
height: 114rpx;
z-index: 2;
}
.poster-qrcode-image {
width: 114rpx;
height: 114rpx;
}
.poster-close-btn {
position: absolute;
left: 50%;
top: 1400rpx;
transform: translateX(-50%);
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.close-icon {
width: 56rpx;
height: 56rpx;
}
.poster-canvas {
position: fixed;
left: -9999px;
top: -9999px;
width: 542rpx;
height: 956rpx;
z-index: -1;
opacity: 0;
}
</
style
>
components/xingmaLab/XingmaLabSharePopup.vue
0 → 100644
View file @
355d62f2
<
template
>
<view
v-if=
"visible"
class=
"share-popup-overlay"
@
tap=
"handleOverlayClick"
>
<view
class=
"share-popup-container"
@
tap
.
stop
>
<!-- 背景 -->
<image
class=
"share-popup-bg"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBtnConBg.png`"
mode="aspectFit">
</image>
<!-- 标题 -->
<view
class=
"share-popup-title"
>
<view
class=
"close-btn"
@
tap=
"handleClose"
>
<image
class=
"close-icon"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBtnConCloseBtn.png`"
mode="aspectFit">
</image>
</view>
</view>
<!-- 按钮区域 -->
<view
class=
"share-popup-buttons"
>
<!-- 分享给好友 -->
<view
class=
"share-button-item"
>
<button
open-type=
"share"
class=
"share-button-trigger"
@
tap=
"handleShareToFriend"
>
<view
class=
"button-icon button-icon-share"
>
<image
class=
"icon-image"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBtnConShareBtn.png`" mode="aspectFit">
</image>
</view>
</button>
</view>
<!-- 生成分享海报 -->
<view
class=
"share-button-item"
@
tap=
"handleGeneratePoster"
>
<view
class=
"button-icon button-icon-poster"
>
<image
class=
"icon-image"
:src=
"`$
{$baseUrl}homepage/Q3Res/xingmaLabPosterBtnConShowPosterBtn.png`" mode="aspectFit">
</image>
</view>
</view>
</view>
</view>
</view>
</
template
>
<
script
setup
>
import
{
getCurrentInstance
}
from
'vue'
const
{
proxy
}
=
getCurrentInstance
()
const
$baseUrl
=
proxy
.
$baseUrl
const
props
=
defineProps
({
visible
:
{
type
:
Boolean
,
default
:
false
}
})
const
emit
=
defineEmits
([
'close'
,
'shareToFriend'
,
'generatePoster'
])
// 关闭弹窗
const
handleClose
=
()
=>
{
emit
(
'close'
)
}
// 点击遮罩关闭
const
handleOverlayClick
=
()
=>
{
handleClose
()
}
// 分享给好友
const
handleShareToFriend
=
()
=>
{
emit
(
'shareToFriend'
)
handleClose
()
}
// 生成分享海报
const
handleGeneratePoster
=
()
=>
{
emit
(
'generatePoster'
)
handleClose
()
}
</
script
>
<
style
lang=
"less"
scoped
>
.share-popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 9999;
}
.share-popup-container {
position: relative;
width: 750rpx;
height: 432rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.share-popup-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.share-popup-title {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-top: 60rpx;
z-index: 1;
}
.title-text {
font-size: 36rpx;
font-weight: 500;
color: #333333;
}
.close-btn {
position: absolute;
right: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.close-icon {
width: 48rpx;
height: 48rpx;
}
.share-popup-buttons {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
padding: 80rpx 60rpx 0;
z-index: 1;
}
.share-button-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.share-button-trigger {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: transparent;
border: none;
padding: 0;
margin: 0;
line-height: normal;
}
.share-button-trigger::after {
border: none;
}
.button-icon {
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.button-icon-share {
width: 148rpx;
height: 202rpx;
}
.button-icon-poster {
width: 170rpx;
height: 202rpx;
}
.icon-image {
width: 100%;
height: 100%;
}
.button-text {
font-size: 28rpx;
color: #333333;
}
</
style
>
pages/XingmaLabDetailPage/XingmaLabDetailPage.vue
View file @
355d62f2
...
...
@@ -42,9 +42,9 @@
@click="handleLikeClick">
</span>
<!-- 分享按钮 -->
<
button
open-type=
"share"
class=
"xingmalabdetailpagebottomconsharebtn"
<
view
class=
"xingmalabdetailpagebottomconsharebtn"
:style=
"
{ backgroundImage: `url(${$baseUrl}${getImageUrl(config.images.bottomConShareBtn)})` }"
@click="handleShareClick">
</
button
>
@click="handleShareClick">
</
view
>
<!-- 未登录时覆盖的授权按钮层 -->
<div
v-if=
"homeStore && !homeStore.isLogin"
class=
"auth-overlay"
>
...
...
@@ -57,6 +57,25 @@
class=
"auth-share-btn phone-auth-btn-cover"
></button>
</div>
</div>
<!-- 分享弹窗 -->
<XingmaLabSharePopup
:visible=
"showSharePopup"
@
close=
"handleCloseSharePopup"
@
shareToFriend=
"handleShareToFriend"
@
generatePoster=
"handleGeneratePoster"
/>
<!-- 海报页面 -->
<XingmaLabPosterPage
:visible=
"showPosterPage"
:imageUrl=
"detailData.imgUrl"
:content=
"detailData.content || '可爱'"
:collectionNumber=
"detailData.bizNo || collectionNumber"
:defaultNickname=
"detailData.nickname || '星妈用户'"
:avatar=
"detailData.avatar || ''"
:recordId=
"recordId || detailData.id || ''"
@
close=
"handleClosePosterPage"
@
download=
"handleDownloadPoster"
/>
</view>
</
template
>
...
...
@@ -83,6 +102,8 @@
useUserStore
}
from
'@/stores/user.js'
import
md
from
'../../md'
;
import
XingmaLabSharePopup
from
'@/components/xingmaLab/XingmaLabSharePopup.vue'
import
XingmaLabPosterPage
from
'@/components/xingmaLab/XingmaLabPosterPage.vue'
// 组件名称
defineOptions
({
...
...
@@ -104,6 +125,10 @@
state
:
null
})
// 分享弹窗和海报页面状态
const
showSharePopup
=
ref
(
false
)
const
showPosterPage
=
ref
(
false
)
// 配置对象
const
config
=
xingmaLabDetailConfig
...
...
@@ -259,22 +284,58 @@
}
const
handleShareClick
=
()
=>
{
//
使用 open-type="share" 时,点击事件可以为空
// 分享内容通过页面配置自动设置
console
.
log
(
'分享按钮被点击'
)
//
TODO:
埋点
//
显示分享弹窗
showSharePopup
.
value
=
true
//
埋点
md
.
sensorComponentLogTake
({
xcxComponentClick
:
"true"
,
pageName
:
"星妈lab-藏品详情页"
,
componentName
:
"藏品详情"
,
componentContent
:
"分享"
});
}
// 手动触发分享(如果需要的话)
// uni.showShareMenu({
// withShareTicket: true,
// menus: ['shareAppMessage'] // 仅分享给好友,禁用朋友圈
// })
// 关闭分享弹窗
const
handleCloseSharePopup
=
()
=>
{
showSharePopup
.
value
=
false
}
// 分享给好友
const
handleShareToFriend
=
()
=>
{
// 埋点
md
.
sensorComponentLogTake
({
xcxComponentClick
:
"true"
,
pageName
:
"星妈lab-藏品详情页"
,
componentName
:
"分享弹窗"
,
componentContent
:
"分享给好友"
});
// 分享功能由 button 的 open-type="share" 自动触发
}
// 生成分享海报
const
handleGeneratePoster
=
()
=>
{
showPosterPage
.
value
=
true
// 埋点
md
.
sensorComponentLogTake
({
xcxComponentClick
:
"true"
,
pageName
:
"星妈lab-藏品详情页"
,
componentName
:
"分享弹窗"
,
componentContent
:
"生成分享海报"
});
}
// 关闭海报页面
const
handleClosePosterPage
=
()
=>
{
showPosterPage
.
value
=
false
}
// 下载海报
const
handleDownloadPoster
=
(
data
)
=>
{
// TODO: 实现海报下载逻辑
// 可以使用 canvas 将海报内容绘制成图片,然后保存到相册
console
.
log
(
'下载海报'
,
data
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment