hookehuyr

feat(分享海报): 启用分享按钮并优化海报布局和日期显示

- 启用课程详情页的分享按钮功能
- 优化分享海报的布局和间距
- 新增日期格式化功能并显示课程日期范围
- 添加底部"扫码了解详情"提示文案
......@@ -23,19 +23,21 @@
<div class="PosterCover">
<img :src="cover_src" alt="课程封面" class="w-full h-full object-cover" crossorigin="anonymous" />
</div>
<!-- 下部信息区:左二维码 + 右文案 -->
<div class="PosterInfo p-4">
<!-- 下部信息区:左二维码 + 右文案 -->
<div class="PosterInfo p-4">
<div class="flex items-start">
<!-- 左侧二维码 -->
<div class="PosterQR mr-4">
<img :src="qr_src" alt="课程二维码" class="w-24 h-24 rounded" crossorigin="anonymous" />
</div>
<!-- 右侧文案 -->
<div class="flex-1">
<div class="text-lg font-semibold text-gray-800 truncate">{{ title_text }}</div>
<div class="text-sm text-gray-500 mt-1 truncate" v-if="subtitle_text">{{ subtitle_text }}</div>
<div class="flex-1 flex flex-col space-y-2 -mt-1">
<div class="text-lg font-semibold text-gray-800 truncate leading-tight -mt-0.5">{{ title_text }}</div>
<div class="text-sm text-gray-500 truncate" v-if="subtitle_text">{{ subtitle_text }}</div>
<div class="text-lg text-gray-400 truncate" v-if="date_range_text">{{ date_range_text }}</div>
</div>
</div>
<div class="mt-4 text-green-600 text-base">扫码了解详情</div>
</div>
</div>
</div>
......@@ -139,11 +141,31 @@ const intro_text = computed(() => {
return text
})
/**
* @function format_date
* @description 将时间字符串转为 `YYYY-MM-DD` 格式;无法解析则返回原字符串。
* @param {string} s 时间字符串
* @returns {string} 处理后的日期字符串
*/
function format_date(s) {
if (!s) return ''
const t = String(s).trim()
if (t.length >= 10) return t.slice(0, 10)
const d = new Date(t)
if (!isNaN(d.getTime())) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${dd}`
}
return t
}
/** 日期范围(若有) */
const date_range_text = computed(() => {
const s = props.course?.start_at || ''
const e = props.course?.end_at || ''
if (s && e) return `${s} ${e}`
const s = props.course?.course_start_time || props.course?.start_at || ''
const e = props.course?.course_end_time || props.course?.end_at || ''
if (s && e) return `${format_date(s)} ${format_date(e)}`
return ''
})
......@@ -292,16 +314,26 @@ async function compose_poster() {
const title_font = 'bold 36px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei"'
const subtitle_font = 'normal 24px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei"'
const date_font = 'normal 28px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei"'
const footnote_font = 'normal 24px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei"'
const measurer = document.createElement('canvas')
const mctx = measurer.getContext('2d')
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)
const subtitle_lines = subtitle_text.value ? wrap_text(mctx, subtitle_text.value, text_max_w, subtitle_font, 32, 1) : []
const total_text_h = (title_lines.length ? 44 : 0) + (subtitle_lines.length ? 32 : 0)
const text_offset_y = Math.max(0, Math.floor((info_body_h - total_text_h) / 2))
const date_lines = date_range_text.value ? wrap_text(mctx, date_range_text.value, text_max_w, date_font, 40, 1) : []
// 文本行间距:用于增大标题/副标题/日期的间隔
const line_gap = 10
const lines_count = (title_lines.length ? 1 : 0) + (subtitle_lines.length ? 1 : 0) + (date_lines.length ? 1 : 0)
const gaps_count = Math.max(0, lines_count - 1)
const total_text_h = (title_lines.length ? 44 : 0)
+ (subtitle_lines.length ? 32 : 0)
+ (date_lines.length ? 40 : 0)
+ gaps_count * line_gap
const text_offset_y = 0
const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1))
const canvas = document.createElement('canvas')
canvas.width = Math.round(width * dpr)
......@@ -371,16 +403,35 @@ async function compose_poster() {
}
ctx.drawImage(qr_canvas, card_x + padding, card_y + cover_h + padding, qr_size, qr_size)
// 文案(右侧,仅标题 + 副标题,整体居中
// 文案(右侧,顶部对齐
let tx = card_x + padding + qr_size + 20
let ty = card_y + cover_h + padding + text_offset_y
let ty = card_y + cover_h + padding
ctx.fillStyle = '#1f2937' // gray-800
ctx.font = title_font
title_lines.forEach(line => { ctx.fillText(line, tx, ty + 34); ty += 44 })
// 增加标题与后续文本的间距
title_lines.forEach(line => { ctx.fillText(line, tx, ty + 34); ty += 44 + line_gap })
ctx.fillStyle = '#6b7280' // gray-500
ctx.font = subtitle_font
subtitle_lines.forEach(line => { ctx.fillText(line, tx, ty + 24); ty += 32 })
// 增加副标题与后续文本的间距
subtitle_lines.forEach(line => { ctx.fillText(line, tx, ty + 24); ty += 32 + line_gap })
ctx.fillStyle = '#9ca3af' // gray-400
ctx.font = date_font
// 日期作为最后一行,不再额外叠加间距
date_lines.forEach(line => { ctx.fillText(line, tx, ty + 30); ty += 40 })
// 脚注:底部绿色提示文案(贴底显示)
ctx.fillStyle = '#10b981' // green-500
ctx.font = footnote_font
let foot_y = card_y + cover_h + info_h - padding - 14
// 保证脚注与上方文本至少保留最小间距
const min_gap = 24
const last_text_bottom_y = ty
if (foot_y - last_text_bottom_y < min_gap) {
foot_y = last_text_bottom_y + min_gap
}
ctx.fillText('扫码了解详情', tx, foot_y)
// 恢复裁剪
ctx.restore()
......
......@@ -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">
......