Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
mlaj
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
hookehuyr
2025-12-08 15:42:42 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
0c3f7559d0795662415c4fd21b579dbe785b36b8
0c3f7559
1 parent
0b58cc81
feat(SharePoster): 优化海报生成样式并移除分享按钮
- 为海报添加圆角边框和阴影效果 - 调整海报内部元素布局和样式 - 移除课程详情页的分享按钮功能
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
75 additions
and
17 deletions
src/components/ui/SharePoster.vue
src/views/courses/CourseDetailPage.vue
src/components/ui/SharePoster.vue
View file @
0c3f755
...
...
@@ -14,7 +14,8 @@
</
div
>
<!--
海报区域:直接使用
Canvas
合成的图片,支持长按保存
-->
<
div
class
=
"PosterCard bg-white rounded-xl shadow-md overflow-hidden mx-auto"
ref
=
"card_ref"
>
<!--
当已生成海报图时,容器不再应用卡片边框与阴影,避免双重边框视觉效果;降级展示仍保留卡片样式
-->
<
div
:
class
=
"poster_img_src ? 'PosterCard mx-auto' : 'PosterCard bg-white rounded-xl shadow-md overflow-hidden mx-auto'"
ref
=
"card_ref"
>
<
img
v
-
if
=
"poster_img_src"
:
src
=
"poster_img_src"
alt
=
"分享海报"
class
=
"w-full h-auto object-contain block"
/>
<!--
生成失败或尚未生成时的降级展示(可长按截图保存)
-->
<
div
v
-
else
>
...
...
@@ -231,6 +232,32 @@ function wrap_text(ctx, text, max_w, font, line_h, max_lines) {
}
/**
* @function rounded_rect_path
* @description 绘制圆角矩形路径(仅定义 path,不进行填充或描边)。
* @param {CanvasRenderingContext2D} ctx 画布上下文
* @param {number} x 起始x
* @param {number} y 起始y
* @param {number} w 宽度
* @param {number} h 高度
* @param {number} r 圆角半径
* @returns {void}
*/
function
rounded_rect_path
(
ctx
,
x
,
y
,
w
,
h
,
r
)
{
const
rr
=
Math
.
max
(
0
,
Math
.
min
(
r
,
Math
.
min
(
w
,
h
)
/
2
))
ctx
.
beginPath
()
ctx
.
moveTo
(
x
+
rr
,
y
)
ctx
.
lineTo
(
x
+
w
-
rr
,
y
)
ctx
.
quadraticCurveTo
(
x
+
w
,
y
,
x
+
w
,
y
+
rr
)
ctx
.
lineTo
(
x
+
w
,
y
+
h
-
rr
)
ctx
.
quadraticCurveTo
(
x
+
w
,
y
+
h
,
x
+
w
-
rr
,
y
+
h
)
ctx
.
lineTo
(
x
+
rr
,
y
+
h
)
ctx
.
quadraticCurveTo
(
x
,
y
+
h
,
x
,
y
+
h
-
rr
)
ctx
.
lineTo
(
x
,
y
+
rr
)
ctx
.
quadraticCurveTo
(
x
,
y
,
x
+
rr
,
y
)
ctx
.
closePath
()
}
/**
* @function compose_poster
* @description 以 Canvas 合成海报:上部封面、左侧二维码、右侧文本信息;生成 dataURL 供长按保存。
* @returns {Promise<void>}
...
...
@@ -247,9 +274,17 @@ async function compose_poster() {
const
width
=
750
const
aspect_ratio
=
1
// 高度 = 宽度 * 1
const
height
=
Math
.
round
(
width
*
aspect_ratio
)
// 调整封面与信息区比例:封面 2/3,高度留出更多信息区避免裁切
const
cover_h
=
Math
.
round
(
height
*
2
/
3
)
const
info_h
=
height
-
cover_h
// 卡片外边距,用于投影显示空间;卡片圆角与投影参数
const
card_margin
=
12
const
card_x
=
card_margin
const
card_y
=
card_margin
const
card_w
=
width
-
card_margin
*
2
const
card_h
=
height
-
card_margin
*
2
const
card_radius
=
16
// 调整封面与信息区比例:封面 2/3,高度留出更多信息区避免裁切(基于卡片内容高度)
const
cover_h
=
Math
.
round
(
card_h
*
2
/
3
)
const
info_h
=
card_h
-
cover_h
const
padding
=
32
const
info_body_h
=
info_h
-
padding
*
2
// 二维码尺寸不超过信息区有效高度,保留少量安全边距,防止底部被裁切
...
...
@@ -260,7 +295,7 @@ async function compose_poster() {
const
measurer
=
document
.
createElement
(
'
canvas
'
)
const
mctx
=
measurer
.
getContext
(
'
2
d
'
)
const
text_max_w
=
width
-
padding
*
2
-
qr_size
-
20
const
text_max_w
=
card_w
-
padding
*
2
-
qr_size
-
20
// 仅测量标题与副标题,并在信息区垂直居中排版
const
title_lines
=
wrap_text
(
mctx
,
title_text
.
value
,
text_max_w
,
title_font
,
44
,
1
)
...
...
@@ -274,26 +309,46 @@ async function compose_poster() {
const
ctx
=
canvas
.
getContext
(
'
2
d
'
)
ctx
.
scale
(
dpr
,
dpr
)
// 背景
// 卡片阴影与背景(白色圆角卡片 + 灰色边框)
ctx
.
save
()
rounded_rect_path
(
ctx
,
card_x
,
card_y
,
card_w
,
card_h
,
card_radius
)
ctx
.
shadowColor
=
'
rgba
(
0
,
0
,
0
,
0
.
12
)
'
ctx
.
shadowBlur
=
12
ctx
.
shadowOffsetX
=
0
ctx
.
shadowOffsetY
=
4
ctx
.
fillStyle
=
'#
ffffff
'
ctx
.
fillRect
(
0
,
0
,
width
,
height
)
ctx
.
fill
()
ctx
.
restore
()
// 卡片边框
ctx
.
save
()
rounded_rect_path
(
ctx
,
card_x
,
card_y
,
card_w
,
card_h
,
card_radius
)
ctx
.
strokeStyle
=
'#
e5e7eb
'
// gray-200
ctx
.
lineWidth
=
2
ctx
.
stroke
()
ctx
.
restore
()
// 卡片内容裁剪区域
ctx
.
save
()
rounded_rect_path
(
ctx
,
card_x
,
card_y
,
card_w
,
card_h
,
card_radius
)
ctx
.
clip
()
// 封面图(对象填充)
const
cover_img
=
await
load_image
(
cover_src
.
value
)
if
(
cover_img
)
{
const
scale
=
Math
.
max
(
width
/
cover_img
.
width
,
cover_h
/
cover_img
.
height
)
const
scale
=
Math
.
max
(
card_w
/
cover_img
.
width
,
cover_h
/
cover_img
.
height
)
const
dw
=
cover_img
.
width
*
scale
const
dh
=
cover_img
.
height
*
scale
const
dx
=
(
width
-
dw
)
/
2
const
dy
=
(
cover_h
-
dh
)
/
2
const
dx
=
card_x
+
(
card_w
-
dw
)
/
2
const
dy
=
card_y
+
(
cover_h
-
dh
)
/
2
ctx
.
drawImage
(
cover_img
,
dx
,
dy
,
dw
,
dh
)
}
else
{
ctx
.
fillStyle
=
'#
f3f4f6
'
// gray-100
ctx
.
fillRect
(
0
,
0
,
width
,
cover_h
)
ctx
.
fillRect
(
card_x
,
card_y
,
card_w
,
cover_h
)
ctx
.
fillStyle
=
'#
9
ca3af
'
// gray-400
ctx
.
textAlign
=
'
center
'
ctx
.
font
=
'
normal
20
px
-
apple
-
system
,
BlinkMacSystemFont
,
"PingFang SC"
,
"Microsoft YaHei"
'
ctx
.
fillText
(
'封面加载失败'
,
width
/
2
,
cover_h
/
2
+
10
)
ctx
.
fillText
(
'封面加载失败'
,
card_x
+
card_w
/
2
,
card_y
+
cover_h
/
2
+
10
)
ctx
.
textAlign
=
'
left
'
}
...
...
@@ -314,11 +369,11 @@ async function compose_poster() {
qctx
.
font
=
`${
Math
.
round
(
16
*
dpr
)
}
px
-
apple
-
system
,
BlinkMacSystemFont
,
"PingFang SC"
,
"Microsoft YaHei"
`
qctx
.
fillText
(
'二维码生成失败'
,
Math
.
round
(
12
*
dpr
),
Math
.
round
((
qr_size
*
dpr
)
/
2
))
}
ctx
.
drawImage
(
qr_canvas
,
padding
,
cover_h
+
padding
,
qr_size
,
qr_size
)
ctx
.
drawImage
(
qr_canvas
,
card_x
+
padding
,
card_y
+
cover_h
+
padding
,
qr_size
,
qr_size
)
// 文案(右侧,仅标题 + 副标题,整体居中)
let
tx
=
padding
+
qr_size
+
20
let
ty
=
cover_h
+
padding
+
text_offset_y
let
tx
=
card_x
+
padding
+
qr_size
+
20
let
ty
=
c
ard_y
+
c
over_h
+
padding
+
text_offset_y
ctx
.
fillStyle
=
'#
1
f2937
'
// gray-800
ctx
.
font
=
title_font
title_lines
.
forEach
(
line
=>
{
ctx
.
fillText
(
line
,
tx
,
ty
+
34
)
;
ty
+=
44
}
)
...
...
@@ -327,6 +382,9 @@ async function compose_poster() {
ctx
.
font
=
subtitle_font
subtitle_lines
.
forEach
(
line
=>
{
ctx
.
fillText
(
line
,
tx
,
ty
+
24
)
;
ty
+=
32
}
)
// 恢复裁剪
ctx
.
restore
()
// 生成 dataURL
try
{
const
data_url
=
canvas
.
toDataURL
(
'
image
/
png
'
)
...
...
src/views/courses/CourseDetailPage.vue
View file @
0c3f755
...
...
@@ -224,7 +224,7 @@
</svg>
咨询
</button>
<button class="flex flex-col items-center text-gray-500 text-xs" @click="open_share_poster">
<
!-- <
button class="flex flex-col items-center text-gray-500 text-xs" @click="open_share_poster">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
...
...
@@ -240,7 +240,7 @@
/>
</svg>
分享
</button>
</button>
-->
</div>
<div class="flex items-center">
<div v-if="!course?.is_buy" class="mr-2">
...
...
Please
register
or
login
to post a comment