hookehuyr

feat(PosterBuilder): 添加海报圆角功能支持

为海报生成器添加圆角功能,支持统一圆角和自定义四角不同圆角
新增 borderRadius 和 borderRadiusGroup 配置参数
使用 Canvas clip() 方法确保圆角效果全局生效
添加测试页面和详细使用文档
# PosterBuilder 海报生成器
## 圆角功能使用说明
### 功能概述
PosterBuilder 组件现在支持为生成的海报添加圆角效果。您可以通过配置参数来控制海报的圆角样式。
### 配置参数
#### 1. 统一圆角 (borderRadius)
为海报四个角设置相同的圆角半径:
```javascript
const config = {
width: 600,
height: 800,
backgroundColor: '#4F46E5',
borderRadius: 30, // 统一圆角半径
// ... 其他配置
}
```
#### 2. 自定义圆角 (borderRadiusGroup)
为海报四个角分别设置不同的圆角半径:
```javascript
const config = {
width: 600,
height: 800,
backgroundColor: '#4F46E5',
borderRadiusGroup: [50, 20, 50, 20], // [左上, 右上, 右下, 左下]
// ... 其他配置
}
```
#### 3. 无圆角(默认)
如果不设置 `borderRadius``borderRadiusGroup`,海报将保持原有的直角样式:
```javascript
const config = {
width: 600,
height: 800,
backgroundColor: '#4F46E5',
// 不设置圆角参数,保持直角
// ... 其他配置
}
```
### 使用示例
```vue
<template>
<PosterBuilder
:config="posterConfig"
:show-loading="true"
@success="onPosterSuccess"
@fail="onPosterFail"
/>
</template>
<script>
import { ref } from 'vue'
import PosterBuilder from '@/components/PosterBuilder/index.vue'
export default {
components: {
PosterBuilder
},
setup() {
const posterConfig = ref({
width: 600,
height: 800,
backgroundColor: '#4F46E5',
borderRadius: 30, // 添加圆角
texts: [
{
text: '圆角海报标题',
x: 300,
y: 100,
fontSize: 32,
color: '#FFFFFF',
textAlign: 'center'
}
]
})
const onPosterSuccess = (result) => {
console.log('海报生成成功:', result.tempFilePath)
}
const onPosterFail = (error) => {
console.error('海报生成失败:', error)
}
return {
posterConfig,
onPosterSuccess,
onPosterFail
}
}
}
</script>
```
### 注意事项
1. **优先级**:如果同时设置了 `borderRadius``borderRadiusGroup`,将优先使用 `borderRadiusGroup`
2. **数组格式**`borderRadiusGroup` 必须是包含4个数字的数组,分别对应 [左上, 右上, 右下, 左下] 四个角的圆角半径
3. **单位**:圆角半径的单位与画布尺寸单位一致
4. **兼容性**:该功能向下兼容,不设置圆角参数时保持原有行为
### 重要修复说明
**问题**:之前版本中,即使设置了 `borderRadius``borderRadiusGroup`,生成的海报四个角仍然是直角。
**原因**:圆角背景被后续绘制的图片(特别是背景图)覆盖,导致圆角效果失效。
**解决方案**:使用 Canvas 的 `clip()` 方法设置全局裁剪路径,确保所有绘制内容都在圆角范围内。
### 测试页面
项目中提供了测试页面 `src/pages/TestPoster/index.vue`,您可以通过该页面测试不同的圆角效果。
### 技术实现细节
1. **裁剪路径设置**:在绘制任何内容之前,先根据圆角配置创建裁剪路径
2. **全局生效**:裁剪路径对后续所有绘制操作(图片、文字、块等)都生效
3. **上下文管理**:使用 `ctx.save()``ctx.restore()` 正确管理画布上下文
\ No newline at end of file
......@@ -11,7 +11,7 @@
import Taro from "@tarojs/taro"
import { defineComponent, onMounted, PropType, ref } from "vue"
import { Image, DrawConfig } from "./types"
import { drawImage, drawText, drawBlock, drawLine } from "./utils/draw"
import { drawImage, drawText, drawBlock, drawLine, _drawRadiusRect, _drawRadiusGroupRect } from "./utils/draw"
import {
toPx,
toRpx,
......@@ -41,8 +41,11 @@ export default defineComponent({
backgroundColor,
texts = [],
blocks = [],
images = [],
lines = [],
debug = false,
borderRadius,
borderRadiusGroup,
} = props.config || {}
const canvasId = getRandomId()
......@@ -125,12 +128,29 @@ export default defineComponent({
canvas.width = width
canvas.height = height
// 如果有圆角配置,设置全局裁剪路径
if (borderRadius || borderRadiusGroup) {
ctx.save() // 保存绘图上下文
if (borderRadiusGroup && borderRadiusGroup.length === 4) {
_drawRadiusGroupRect(
{ x: 0, y: 0, w: width, h: height, g: borderRadiusGroup },
{ ctx }
)
} else if (borderRadius) {
_drawRadiusRect(
{ x: 0, y: 0, w: width, h: height, r: borderRadius },
{ ctx }
)
}
ctx.clip() // 设置裁剪路径
}
// 设置画布底色
if (backgroundColor) {
ctx.save() // 保存绘图上下文
const grd = getLinearColor(ctx, backgroundColor, 0, 0, width, height)
ctx.fillStyle = grd // 设置填充颜色
ctx.fillRect(0, 0, width, height) // 填充一个矩形
ctx.fillRect(0, 0, width, height) // 填充矩形
ctx.restore() // 恢复之前保存的绘图上下文
}
// 将要画的方块、文字、线条放进队列数组
......@@ -176,6 +196,11 @@ export default defineComponent({
}
}
// 如果设置了圆角裁剪,恢复画布上下文
if (borderRadius || borderRadiusGroup) {
ctx.restore() // 恢复之前保存的绘图上下文
}
setTimeout(() => {
getTempFile(canvas) // 需要做延时才能能正常加载图片
}, 300)
......
......@@ -71,6 +71,8 @@ export type DrawConfig = {
height: number;
backgroundColor?: string;
debug?: boolean;
borderRadius?: number;
borderRadiusGroup?: number[];
blocks?: Block[];
texts?: Text[];
images?: Image[];
......
......@@ -28,8 +28,8 @@
</view>
<!-- 海报预览区域 - 正常状态 -->
<view v-if="pageState === 'normal'" class="flex-1 mx-4 mb-2 bg-white rounded-lg shadow-sm relative" style="overflow: visible;">
<view class="h-full relative bg-gray-100 flex items-center justify-center">
<view v-if="pageState === 'normal'" class="flex-1 mx-4 mb-2 bg-white rounded-lg relative" style="overflow: visible;">
<view class="h-full relative flex items-center justify-center">
<view v-if="currentPoster.path" class="w-full h-full relative">
<image
:src="currentPoster.path"
......@@ -41,9 +41,9 @@
{{ posterList[currentPosterIndex]?.title || '海报生成中' }}
</view> -->
<!-- 点击预览提示 -->
<view @tap="previewPoster" class="absolute bottom-2 right-2 bg-black bg-opacity-50 text-white text-xs px-2 py-1 rounded">
<!-- <view @tap="previewPoster" class="absolute bottom-2 right-2 bg-black bg-opacity-50 text-white text-xs px-2 py-1 rounded">
点击预览
</view>
</view> -->
</view>
</view>
......@@ -302,6 +302,7 @@ const posterConfig = computed(() => {
height: 1334,
backgroundColor: '#f5f5f5',
debug: false,
borderRadius: 15,
images: [
// 背景图
{
......
<template>
<view class="test-poster-page">
<view class="mb-4">
<text class="text-lg font-bold">海报圆角测试</text>
</view>
<!-- 测试按钮 -->
<view class="flex flex-col gap-4 mb-6">
<nut-button @click="generateNormalPoster" type="primary">
生成普通海报(无圆角)
</nut-button>
<nut-button @click="generateRoundPoster" type="success">
生成圆角海报(统一圆角)
</nut-button>
<nut-button @click="generateCustomRoundPoster" type="warning">
生成自定义圆角海报(四角不同)
</nut-button>
</view>
<!-- 海报生成组件 -->
<PosterBuilder
v-if="showPoster"
:config="posterConfig"
:show-loading="true"
@success="onPosterSuccess"
@fail="onPosterFail"
/>
<!-- 生成的海报预览 -->
<view v-if="posterUrl" class="mt-6">
<text class="text-base font-semibold mb-2">生成的海报:</text>
<image
:src="posterUrl"
mode="widthFix"
class="w-full max-w-xs mx-auto block"
/>
</view>
</view>
</template>
<script>
import { defineComponent, ref } from 'vue'
import Taro from '@tarojs/taro'
import PosterBuilder from '../../components/PosterBuilder/index.vue'
export default defineComponent({
name: 'TestPoster',
components: {
PosterBuilder
},
setup() {
const showPoster = ref(false)
const posterUrl = ref('')
const posterConfig = ref({})
/**
* 生成普通海报(无圆角)
*/
const generateNormalPoster = () => {
posterConfig.value = {
width: 600,
height: 800,
backgroundColor: '#4F46E5',
texts: [
{
text: '普通海报测试',
x: 300,
y: 100,
fontSize: 32,
color: '#FFFFFF',
textAlign: 'center',
fontWeight: 'bold'
},
{
text: '这是一个没有圆角的海报',
x: 300,
y: 200,
fontSize: 24,
color: '#E5E7EB',
textAlign: 'center'
}
],
blocks: [
{
x: 50,
y: 300,
width: 500,
height: 200,
backgroundColor: '#FFFFFF',
borderRadius: 0
}
]
}
showPoster.value = true
}
/**
* 生成圆角海报(统一圆角)
*/
const generateRoundPoster = () => {
posterConfig.value = {
width: 600,
height: 800,
backgroundColor: '#10B981',
borderRadius: 30, // 统一圆角
texts: [
{
text: '圆角海报测试',
x: 300,
y: 100,
fontSize: 32,
color: '#FFFFFF',
textAlign: 'center',
fontWeight: 'bold'
},
{
text: '这是一个有统一圆角的海报',
x: 300,
y: 200,
fontSize: 24,
color: '#E5E7EB',
textAlign: 'center'
}
],
blocks: [
{
x: 50,
y: 300,
width: 500,
height: 200,
backgroundColor: '#FFFFFF',
borderRadius: 20
}
]
}
showPoster.value = true
}
/**
* 生成自定义圆角海报(四角不同)
*/
const generateCustomRoundPoster = () => {
posterConfig.value = {
width: 600,
height: 800,
backgroundColor: '#F59E0B',
borderRadiusGroup: [50, 20, 50, 20], // 左上、右上、右下、左下
texts: [
{
text: '自定义圆角海报',
x: 300,
y: 100,
fontSize: 32,
color: '#FFFFFF',
textAlign: 'center',
fontWeight: 'bold'
},
{
text: '四个角有不同的圆角半径',
x: 300,
y: 200,
fontSize: 24,
color: '#1F2937',
textAlign: 'center'
}
],
blocks: [
{
x: 50,
y: 300,
width: 500,
height: 200,
backgroundColor: '#FFFFFF',
borderRadiusGroup: [30, 10, 30, 10]
}
]
}
showPoster.value = true
}
/**
* 海报生成成功回调
*/
const onPosterSuccess = (result) => {
console.log('海报生成成功:', result)
posterUrl.value = result.tempFilePath
showPoster.value = false
Taro.showToast({
title: '海报生成成功',
icon: 'success'
})
}
/**
* 海报生成失败回调
*/
const onPosterFail = (error) => {
console.error('海报生成失败:', error)
showPoster.value = false
Taro.showToast({
title: '海报生成失败',
icon: 'error'
})
}
return {
showPoster,
posterUrl,
posterConfig,
generateNormalPoster,
generateRoundPoster,
generateCustomRoundPoster,
onPosterSuccess,
onPosterFail
}
}
})
</script>
<style lang="less" scoped>
.test-poster-page {
padding: 32rpx;
min-height: 100vh;
background-color: #f5f5f5;
}
</style>
\ No newline at end of file