hookehuyr

feat(ActivitiesCover): 添加活动分享功能及海报生成组件

- 新增分享按钮及分享弹窗组件
- 实现海报生成功能,支持保存至相册
- 添加分享选项包括微信分享和海报生成
- 优化图片下载工具函数,增加重试机制
- 更新babel配置支持TypeScript
- 添加相关SVG图标资源
...@@ -4,7 +4,7 @@ module.exports = { ...@@ -4,7 +4,7 @@ module.exports = {
4 presets: [ 4 presets: [
5 ['taro', { 5 ['taro', {
6 framework: 'vue3', 6 framework: 'vue3',
7 - ts: false, 7 + ts: true,
8 compiler: 'webpack5', 8 compiler: 'webpack5',
9 }] 9 }]
10 ] 10 ]
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
37 "license": "MIT", 37 "license": "MIT",
38 "dependencies": { 38 "dependencies": {
39 "@babel/runtime": "^7.7.7", 39 "@babel/runtime": "^7.7.7",
40 + "@nutui/icons-vue": "^0.1.1",
40 "@nutui/icons-vue-taro": "^0.0.9", 41 "@nutui/icons-vue-taro": "^0.0.9",
41 "@nutui/nutui-taro": "^4.3.13", 42 "@nutui/nutui-taro": "^4.3.13",
42 "@tarojs/components": "4.1.2", 43 "@tarojs/components": "4.1.2",
...@@ -64,6 +65,7 @@ ...@@ -64,6 +65,7 @@
64 "@tarojs/cli": "4.1.2", 65 "@tarojs/cli": "4.1.2",
65 "@tarojs/taro-loader": "4.1.2", 66 "@tarojs/taro-loader": "4.1.2",
66 "@tarojs/webpack5-runner": "4.1.2", 67 "@tarojs/webpack5-runner": "4.1.2",
68 + "@types/node": "^24.3.0",
67 "@types/webpack-env": "^1.13.6", 69 "@types/webpack-env": "^1.13.6",
68 "@vue/babel-plugin-jsx": "^1.0.6", 70 "@vue/babel-plugin-jsx": "^1.0.6",
69 "@vue/compiler-sfc": "^3.0.0", 71 "@vue/compiler-sfc": "^3.0.0",
...@@ -75,6 +77,7 @@ ...@@ -75,6 +77,7 @@
75 "postcss": "^8.5.6", 77 "postcss": "^8.5.6",
76 "style-loader": "1.3.0", 78 "style-loader": "1.3.0",
77 "tailwindcss": "^3.4.0", 79 "tailwindcss": "^3.4.0",
80 + "typescript": "^5.9.2",
78 "unplugin-vue-components": "^0.26.0", 81 "unplugin-vue-components": "^0.26.0",
79 "vue-loader": "^17.0.0", 82 "vue-loader": "^17.0.0",
80 "weapp-tailwindcss": "^4.1.10", 83 "weapp-tailwindcss": "^4.1.10",
......
This diff could not be displayed because it is too large.
1 +<svg width="949" height="108" viewBox="0 0 949 108" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <rect width="949" height="2" y="53" fill="#E5E7EB"/>
3 + <circle cx="100" cy="54" r="8" fill="#3B82F6"/>
4 + <circle cx="200" cy="54" r="6" fill="#10B981"/>
5 + <circle cx="300" cy="54" r="8" fill="#F59E0B"/>
6 + <circle cx="400" cy="54" r="6" fill="#EF4444"/>
7 + <circle cx="500" cy="54" r="8" fill="#8B5CF6"/>
8 + <circle cx="600" cy="54" r="6" fill="#06B6D4"/>
9 + <circle cx="700" cy="54" r="8" fill="#84CC16"/>
10 + <circle cx="800" cy="54" r="6" fill="#F97316"/>
11 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M17.5 0C7.835 0 0 7.835 0 17.5C0 30.625 17.5 40 17.5 40S35 30.625 35 17.5C35 7.835 27.165 0 17.5 0Z" fill="#4CAF50"/>
3 + <circle cx="17.5" cy="17.5" r="7" fill="#fff"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M18 16.08C17.24 16.08 16.56 16.38 16.04 16.85L8.91 12.7C8.96 12.47 9 12.24 9 12C9 11.76 8.96 11.53 8.91 11.3L15.96 7.19C16.5 7.69 17.21 8 18 8C19.66 8 21 6.66 21 5C21 3.34 19.66 2 18 2C16.34 2 15 3.34 15 5C15 5.24 15.04 5.47 15.09 5.7L8.04 9.81C7.5 9.31 6.79 9 6 9C4.34 9 3 10.34 3 12C3 13.66 4.34 15 6 15C6.79 15 7.5 14.69 8.04 14.19L15.16 18.34C15.11 18.55 15.08 18.77 15.08 19C15.08 20.61 16.39 21.92 18 21.92C19.61 21.92 20.92 20.61 20.92 19C20.92 17.39 19.61 16.08 18 16.08Z" fill="currentColor"/>
3 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <circle cx="20" cy="20" r="18" fill="#FF6B35" stroke="#fff" stroke-width="2"/>
3 + <path d="M20 8v12l8 4" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 /* eslint-disable prefer-destructuring */ 1 /* eslint-disable prefer-destructuring */
2 -import Taro, { CanvasContext, CanvasGradient } from '@tarojs/taro'; 2 +import Taro from '@tarojs/taro'
3 3
4 -declare const wx: any; 4 +// 全局变量声明
5 +// eslint-disable-next-line no-undef
6 +const wx = globalThis.wx || {};
5 7
6 /** 8 /**
7 * @description 生成随机字符串 9 * @description 生成随机字符串
...@@ -128,28 +130,70 @@ export const toRpx = (px, factor = getFactor()) => ...@@ -128,28 +130,70 @@ export const toRpx = (px, factor = getFactor()) =>
128 /** 130 /**
129 * 下载图片资源 131 * 下载图片资源
130 * @param { string } url 132 * @param { string } url
133 + * @param { number } retryCount - 重试次数
131 * @returns { Promise } 134 * @returns { Promise }
132 */ 135 */
133 -export function downImage(url) { 136 +export function downImage(url, retryCount = 2) {
134 - return new Promise<string>((resolve, reject) => { 137 + return new Promise((resolve, reject) => {
135 // eslint-disable-next-line no-undef 138 // eslint-disable-next-line no-undef
136 if (/^http/.test(url) && !new RegExp(wx.env.USER_DATA_PATH).test(url)) { 139 if (/^http/.test(url) && !new RegExp(wx.env.USER_DATA_PATH).test(url)) {
137 // wx.env.USER_DATA_PATH 文件系统中的用户目录路径 140 // wx.env.USER_DATA_PATH 文件系统中的用户目录路径
138 - Taro.downloadFile({ 141 + const attemptDownload = (attempt = 0) => {
139 - url: mapHttpToHttps(url), 142 + Taro.downloadFile({
140 - success: (res) => { 143 + url: mapHttpToHttps(url),
141 - if (res.statusCode === 200) { 144 + timeout: 10000, // 设置10秒超时
142 - resolve(res.tempFilePath); 145 + success: (res) => {
143 - } else { 146 + if (res.statusCode === 200) {
144 - console.log('下载失败', res); 147 + resolve(res.tempFilePath);
145 - reject(res); 148 + } else {
149 + // console.log('下载失败', res);
150 + if (attempt < retryCount) {
151 + // console.log(`重试下载图片,第${attempt + 1}次重试`);
152 + setTimeout(() => attemptDownload(attempt + 1), 1000);
153 + } else {
154 + reject(res);
155 + }
156 + }
157 + },
158 + fail(err) {
159 + // console.log('下载失败了', err);
160 + // 如果是代理连接问题,尝试使用原始URL
161 + if (err.errMsg && err.errMsg.includes('127.0.0.1:7890')) {
162 + // console.log('检测到代理问题,尝试使用原始URL');
163 + if (attempt < retryCount) {
164 + setTimeout(() => {
165 + Taro.downloadFile({
166 + url: url, // 使用原始URL,不转换https
167 + timeout: 10000,
168 + success: (res) => {
169 + if (res.statusCode === 200) {
170 + resolve(res.tempFilePath);
171 + } else {
172 + reject(res);
173 + }
174 + },
175 + fail: () => {
176 + if (attempt + 1 < retryCount) {
177 + attemptDownload(attempt + 1);
178 + } else {
179 + reject(err);
180 + }
181 + }
182 + });
183 + }, 1000);
184 + } else {
185 + reject(err);
186 + }
187 + } else if (attempt < retryCount) {
188 + // console.log(`重试下载图片,第${attempt + 1}次重试`);
189 + setTimeout(() => attemptDownload(attempt + 1), 1000);
190 + } else {
191 + reject(err);
192 + }
146 } 193 }
147 - }, 194 + });
148 - fail(err) { 195 + };
149 - console.log('下载失败了', err); 196 + attemptDownload();
150 - reject(err);
151 - }
152 - });
153 } else { 197 } else {
154 resolve(url); // 支持本地地址 198 resolve(url); // 支持本地地址
155 } 199 }
...@@ -191,7 +235,7 @@ export const getImageInfo = (item, index) => ...@@ -191,7 +235,7 @@ export const getImageInfo = (item, index) =>
191 borderColor: item.borderColor, 235 borderColor: item.borderColor,
192 borderRadiusGroup: item.borderRadiusGroup, 236 borderRadiusGroup: item.borderRadiusGroup,
193 zIndex: typeof zIndex !== 'undefined' ? zIndex : index, 237 zIndex: typeof zIndex !== 'undefined' ? zIndex : index,
194 - imgPath: url, 238 + imgPath: imgPath, // 使用下载后的临时文件路径
195 sx, 239 sx,
196 sy, 240 sy,
197 sw: imgWidth - sx * 2, 241 sw: imgWidth - sx * 2,
...@@ -204,7 +248,7 @@ export const getImageInfo = (item, index) => ...@@ -204,7 +248,7 @@ export const getImageInfo = (item, index) =>
204 resolve(result); 248 resolve(result);
205 }) 249 })
206 .catch((err) => { 250 .catch((err) => {
207 - console.log('读取图片信息失败', err); 251 + // console.log('读取图片信息失败', err);
208 reject(err); 252 reject(err);
209 }) 253 })
210 ); 254 );
...@@ -222,7 +266,7 @@ export const getImageInfo = (item, index) => ...@@ -222,7 +266,7 @@ export const getImageInfo = (item, index) =>
222 */ 266 */
223 // TODO: 待优化, 支持所有角度,多个颜色的线性渐变 267 // TODO: 待优化, 支持所有角度,多个颜色的线性渐变
224 export function getLinearColor( 268 export function getLinearColor(
225 - ctx: CanvasContext, 269 + ctx,
226 color, 270 color,
227 startX, 271 startX,
228 startY, 272 startY,
...@@ -238,7 +282,7 @@ export function getLinearColor( ...@@ -238,7 +282,7 @@ export function getLinearColor(
238 console.warn('坐标或者宽高只支持数字'); 282 console.warn('坐标或者宽高只支持数字');
239 return color; 283 return color;
240 } 284 }
241 - let grd: CanvasGradient | string = color; 285 + let grd = color;
242 if (color.includes('linear-gradient')) { 286 if (color.includes('linear-gradient')) {
243 // fillStyle 不支持线性渐变色 287 // fillStyle 不支持线性渐变色
244 const colorList = color.match(/\((\d+)deg,\s(.+)\s\d+%,\s(.+)\s\d+%/); 288 const colorList = color.match(/\((\d+)deg,\s(.+)\s\d+%,\s(.+)\s\d+%/);
...@@ -263,8 +307,8 @@ export function getLinearColor( ...@@ -263,8 +307,8 @@ export function getLinearColor(
263 } else { 307 } else {
264 throw new Error('只支持0 <= 颜色弧度 <= 180'); 308 throw new Error('只支持0 <= 颜色弧度 <= 180');
265 } 309 }
266 - (grd as CanvasGradient).addColorStop(0, color1); 310 + grd.addColorStop(0, color1);
267 - (grd as CanvasGradient).addColorStop(1, color2); 311 + grd.addColorStop(1, color2);
268 } 312 }
269 return grd; 313 return grd;
270 } 314 }
......
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M18 16.08C17.24 16.08 16.56 16.38 16.04 16.85L8.91 12.7C8.96 12.47 9 12.24 9 12C9 11.76 8.96 11.53 8.91 11.3L15.96 7.19C16.5 7.69 17.21 8 18 8C19.66 8 21 6.66 21 5C21 3.34 19.66 2 18 2C16.34 2 15 3.34 15 5C15 5.24 15.04 5.47 15.09 5.7L8.04 9.81C7.5 9.31 6.79 9 6 9C4.34 9 3 10.34 3 12C3 13.66 4.34 15 6 15C6.79 15 7.5 14.69 8.04 14.19L15.16 18.34C15.11 18.55 15.08 18.77 15.08 19C15.08 20.61 16.39 21.92 18 21.92C19.61 21.92 20.92 20.61 20.92 19C20.92 17.39 19.61 16.08 18 16.08Z" fill="#666666"/>
3 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="949" height="108" viewBox="0 0 949 108" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <defs>
3 + <pattern id="dots" patternUnits="userSpaceOnUse" width="20" height="20">
4 + <circle cx="10" cy="10" r="2" fill="#E5E5E5"/>
5 + </pattern>
6 + </defs>
7 + <rect width="949" height="108" fill="url(#dots)"/>
8 + <line x1="0" y1="54" x2="949" y2="54" stroke="#CCCCCC" stroke-width="2" stroke-dasharray="10,5"/>
9 +</svg>
...\ No newline at end of file ...\ No newline at end of file
...@@ -55,6 +55,27 @@ ...@@ -55,6 +55,27 @@
55 } 55 }
56 } 56 }
57 57
58 +// 分享按钮
59 +.share-button {
60 + position: absolute;
61 + top: 40rpx;
62 + right: 40rpx;
63 + width: 80rpx;
64 + height: 80rpx;
65 + background-color: rgba(0, 0, 0, 0.5);
66 + border-radius: 50%;
67 + display: flex;
68 + align-items: center;
69 + justify-content: center;
70 + color: white;
71 + font-size: 32rpx;
72 + z-index: 10;
73 +
74 + &:active {
75 + background-color: rgba(0, 0, 0, 0.7);
76 + }
77 +}
78 +
58 // 详情区域 79 // 详情区域
59 .details-section { 80 .details-section {
60 flex: 1; 81 flex: 1;
...@@ -189,7 +210,112 @@ ...@@ -189,7 +210,112 @@
189 box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3); 210 box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
190 } 211 }
191 } 212 }
213 +}
214 +
215 +// 弹窗样式
216 +.share-popup {
217 + .nut-popup__content {
218 + border-radius: 24rpx 24rpx 0 0;
219 + padding: 40rpx;
220 + }
221 +}
222 +
223 +.share-title {
224 + font-size: 32rpx;
225 + font-weight: bold;
226 + text-align: center;
227 + margin-bottom: 40rpx;
228 + color: #333;
229 +}
230 +
231 +.share-options {
232 + display: flex;
233 + justify-content: space-around;
234 + margin-bottom: 40rpx;
235 +}
236 +
237 +.share-option {
238 + display: flex;
239 + flex-direction: column;
240 + align-items: center;
241 + padding: 20rpx;
242 + border-radius: 12rpx;
243 +
244 + &:active {
245 + background-color: #f5f5f5;
246 + }
247 +}
248 +
249 +.share-icon {
250 + width: 80rpx;
251 + height: 80rpx;
252 + margin-bottom: 16rpx;
253 + border-radius: 12rpx;
254 + background-color: #1890ff;
255 + display: flex;
256 + align-items: center;
257 + justify-content: center;
258 + color: white;
259 + font-size: 36rpx;
260 +}
261 +
262 +.share-text {
263 + font-size: 24rpx;
264 + color: #666;
265 +}
266 +
267 +.cancel-button {
268 + width: 100%;
269 + height: 88rpx;
270 + border-radius: 44rpx;
271 + font-size: 32rpx;
272 + background-color: #f5f5f5;
273 + color: #666;
274 + border: none;
275 +}
276 +
277 +// 海报预览弹窗
278 +.poster-preview-popup {
279 + .nut-popup__content {
280 + width: 90%;
281 + max-width: 600rpx;
282 + border-radius: 24rpx;
283 + padding: 40rpx;
284 + background-color: white;
285 + }
286 +}
287 +
288 +.poster-preview {
289 + width: 100%;
290 + border-radius: 12rpx;
291 + margin-bottom: 40rpx;
292 +}
192 293
294 +.preview-actions {
295 + display: flex;
296 + gap: 20rpx;
297 +}
298 +
299 +.preview-button {
300 + flex: 1;
301 + height: 80rpx;
302 + border-radius: 40rpx;
303 + font-size: 28rpx;
304 +
305 + &.primary {
306 + background-color: #1890ff;
307 + color: white;
308 + border: none;
309 + }
310 +
311 + &.secondary {
312 + background-color: #f5f5f5;
313 + color: #666;
314 + border: none;
315 + }
316 + }
317 +
318 +.join-button {
193 &.nut-button--loading { 319 &.nut-button--loading {
194 opacity: 0.7; 320 opacity: 0.7;
195 } 321 }
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-08-28 15:16:04 4 + * @LastEditTime: 2025-08-29 00:53:27
5 * @FilePath: /lls_program/src/pages/ActivitiesCover/index.vue 5 * @FilePath: /lls_program/src/pages/ActivitiesCover/index.vue
6 * @Description: 活动海报页面 - 展示活动信息并处理定位授权 6 * @Description: 活动海报页面 - 展示活动信息并处理定位授权
7 --> 7 -->
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
21 <view class="activity-subtitle">{{ activityData.subtitle }}</view> 21 <view class="activity-subtitle">{{ activityData.subtitle }}</view>
22 <view class="activity-date">{{ activityData.dateRange }}</view> 22 <view class="activity-date">{{ activityData.dateRange }}</view>
23 </view> 23 </view>
24 +
25 + <!-- 分享按钮 -->
26 + <view @tap="shareActivity" class="share-button">
27 + <!-- <nut-icon name="share" color="white" size="20" /> -->
28 + <text>分享</text>
29 + </view>
24 </view> 30 </view>
25 31
26 <!-- 活动详情区域 --> 32 <!-- 活动详情区域 -->
...@@ -73,6 +79,76 @@ ...@@ -73,6 +79,76 @@
73 79
74 <!-- 底部导航 --> 80 <!-- 底部导航 -->
75 <BottomNav /> 81 <BottomNav />
82 +
83 + <!-- 分享选项弹窗 -->
84 + <nut-popup
85 + v-model:visible="show_share"
86 + position="bottom"
87 + :style="{ height: '40%' }"
88 + class="share-popup"
89 + >
90 + <div class="share-title">立即分享给好友</div>
91 + <div class="share-options">
92 + <div
93 + v-for="option in share_options"
94 + :key="option.name"
95 + class="share-option"
96 + @click="onSelectShare({ detail: option })"
97 + >
98 + <div class="share-icon">{{ option.icon }}</div>
99 + <div class="share-text">{{ option.name }}</div>
100 + </div>
101 + </div>
102 + <nut-button class="cancel-button" @click="onCancelShare">
103 + 取消
104 + </nut-button>
105 + </nut-popup>
106 +
107 + <!-- 海报预览弹窗 -->
108 + <nut-popup
109 + v-model:visible="show_post"
110 + position="center"
111 + class="poster-preview-popup"
112 + >
113 + <view class="wrapper">
114 + <view class="preview-area" @click="onClickPost">
115 + <image v-if="posterPath" :src="posterPath" mode="widthFix" />
116 + </view>
117 + </view>
118 + </nut-popup>
119 +
120 + <!-- 海报生成组件 -->
121 + <PosterBuilder
122 + v-if="startDraw"
123 + custom-style="position: fixed; left: 200%;"
124 + :config="base"
125 + @success="drawSuccess"
126 + @fail="drawFail"
127 + />
128 +
129 + <!-- 保存选项弹窗 -->
130 + <nut-popup
131 + v-model:visible="show_save"
132 + position="bottom"
133 + :style="{ height: '30%' }"
134 + class="share-popup"
135 + >
136 + <div class="share-title">保存选项</div>
137 + <div class="share-options">
138 + <div
139 + v-for="option in actions_save"
140 + :key="option.name"
141 + class="share-option"
142 + @click="onSelectSave({ detail: option })"
143 + >
144 + <div class="share-icon">💾</div>
145 + <div class="share-text">{{ option.name }}</div>
146 + </div>
147 + </div>
148 + <nut-button class="cancel-button" @click="onCancelSave">
149 + 取消
150 + </nut-button>
151 + </nut-popup>
76 </view> 152 </view>
77 </template> 153 </template>
78 154
...@@ -82,6 +158,8 @@ import { ref, onMounted } from "vue" ...@@ -82,6 +158,8 @@ import { ref, onMounted } from "vue"
82 import Taro from '@tarojs/taro' 158 import Taro from '@tarojs/taro'
83 import "./index.less" 159 import "./index.less"
84 import BottomNav from '../../components/BottomNav.vue' 160 import BottomNav from '../../components/BottomNav.vue'
161 +import PosterBuilder from '../../components/PosterBuilder/index.vue'
162 +import icon_share from '@/images/icon/share.svg'
85 163
86 /** 164 /**
87 * 活动海报页面组件 165 * 活动海报页面组件
...@@ -93,6 +171,30 @@ const hasLocationAuth = ref(false) // 是否已授权定位 ...@@ -93,6 +171,30 @@ const hasLocationAuth = ref(false) // 是否已授权定位
93 const isJoining = ref(false) // 是否正在加入活动 171 const isJoining = ref(false) // 是否正在加入活动
94 const userLocation = ref({ lng: null, lat: null }) // 用户位置信息 172 const userLocation = ref({ lng: null, lat: null }) // 用户位置信息
95 173
174 +// 海报生成相关状态
175 +const show_share = ref(false) // 显示分享弹窗
176 +const show_post = ref(false) // 显示海报预览
177 +const show_save = ref(false) // 显示保存弹窗
178 +const startDraw = ref(false) // 开始绘制海报
179 +const posterPath = ref('') // 海报路径
180 +const nickname = ref('老来赛用户') // 用户昵称
181 +const avatar = ref('https://cdn.ipadbiz.cn/icon/tou@2x.png') // 用户头像
182 +
183 +// 分享选项
184 +const share_options = [
185 + { name: '微信', icon: 'wechat', openType: 'share' },
186 + { name: '生成海报', icon: icon_share },
187 +]
188 +
189 +// 保存选项
190 +const actions_save = ref([{
191 + name: '保存至相册'
192 +}])
193 +
194 +// 海报配置
195 +let base = {}
196 +let qrcode_url = 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png' // Mock二维码
197 +
96 // Mock活动数据 198 // Mock活动数据
97 const activityData = ref({ 199 const activityData = ref({
98 title: '南京路商圈时尚Citywalk', 200 title: '南京路商圈时尚Citywalk',
...@@ -210,6 +312,329 @@ const handleJoinActivity = async () => { ...@@ -210,6 +312,329 @@ const handleJoinActivity = async () => {
210 } 312 }
211 } 313 }
212 314
315 +/**
316 + * 分享活动
317 + */
318 +const shareActivity = () => {
319 + show_share.value = true
320 +}
321 +
322 +/**
323 + * 取消分享
324 + */
325 +const onCancelShare = () => {
326 + show_share.value = false
327 +}
328 +
329 +/**
330 + * 选择分享方式
331 + */
332 +const onSelectShare = ({ detail }) => {
333 + show_share.value = false
334 + if (detail.name === '生成海报') {
335 + show_post.value = true
336 + startGeneratePoster()
337 + }
338 +}
339 +
340 +/**
341 + * 点击海报预览
342 + */
343 +const onClickPost = () => {
344 + show_save.value = true
345 +}
346 +
347 +/**
348 + * 取消保存
349 + */
350 +const onCancelSave = () => {
351 + show_save.value = false
352 + show_post.value = false
353 +}
354 +
355 +/**
356 + * 选择保存方式
357 + */
358 +const onSelectSave = ({ detail }) => {
359 + if (detail.name === '保存至相册') {
360 + show_save.value = false
361 + show_post.value = false
362 + savePoster()
363 + }
364 +}
365 +
366 +/**
367 + * 开始生成海报
368 + */
369 +const startGeneratePoster = async () => {
370 + // 配置海报参数
371 + base = {
372 + width: 1024,
373 + height: 1334,
374 + backgroundColor: '',
375 + debug: false,
376 + blocks: [
377 + { // 上部分canvas画布高度
378 + x: 40,
379 + y: 20,
380 + width: 950,
381 + height: 950,
382 + paddingLeft: 0,
383 + paddingRight: 0,
384 + borderWidth: 1,
385 + borderColor: '#fff',
386 + backgroundColor: '#fff',
387 + borderRadiusGroup: [16, 16, 0, 0],
388 + },
389 + { // 活动时间背景图
390 + x: 40,
391 + y: 730,
392 + height: 75,
393 + paddingLeft: 80,
394 + paddingRight: 0,
395 + borderWidth: 0,
396 + text: {
397 + x: 0,
398 + y: 0,
399 + text: activityData.value.dateRange,
400 + fontSize: 40,
401 + color: '#222',
402 + opacity: 1,
403 + baseLine: 'top',
404 + lineHeight: 48,
405 + lineNum: 2,
406 + textAlign: 'left',
407 + zIndex: 0,
408 + },
409 + backgroundColor: '#FFF9F3',
410 + borderRadiusGroup: [0, 25, 25, 0],
411 + },
412 + { // 活动地点背景图
413 + x: 40,
414 + y: 830,
415 + height: 75,
416 + paddingLeft: 80,
417 + paddingRight: 0,
418 + borderWidth: 0,
419 + text: {
420 + x: 0,
421 + y: 0,
422 + text: '上海市黄浦区南京东路',
423 + fontSize: 40,
424 + color: '#222',
425 + opacity: 1,
426 + baseLine: 'top',
427 + lineHeight: 48,
428 + lineNum: 2,
429 + textAlign: 'left',
430 + zIndex: 0,
431 + },
432 + backgroundColor: '#FFF9F3',
433 + borderRadiusGroup: [0, 25, 25, 0],
434 + },
435 + { // 下部分canvas画布高度
436 + x: 40,
437 + y: 1060,
438 + width: 950,
439 + height: 250,
440 + paddingLeft: 0,
441 + paddingRight: 0,
442 + borderWidth: 1,
443 + borderColor: '#fff',
444 + backgroundColor: '#fff',
445 + borderRadiusGroup: [0, 0, 16, 16],
446 + }
447 + ],
448 + texts: [
449 + {
450 + x: 80,
451 + y: 630,
452 + text: activityData.value.title,
453 + fontSize: 50,
454 + color: '#000',
455 + opacity: 1,
456 + baseLine: 'middle',
457 + lineHeight: 60,
458 + lineNum: 2,
459 + textAlign: 'left',
460 + width: 800,
461 + zIndex: 999,
462 + fontFamily: 'Monospace',
463 + },
464 + {
465 + x: 135,
466 + y: 770,
467 + text: activityData.value.dateRange,
468 + fontSize: 40,
469 + color: '#222',
470 + opacity: 1,
471 + baseLine: 'middle',
472 + lineHeight: 48,
473 + lineNum: 2,
474 + textAlign: 'left',
475 + zIndex: 999,
476 + },
477 + {
478 + x: 135,
479 + y: 870,
480 + text: '上海市黄浦区南京东路',
481 + fontSize: 40,
482 + color: '#222',
483 + opacity: 1,
484 + baseLine: 'middle',
485 + lineHeight: 48,
486 + lineNum: 2,
487 + textAlign: 'left',
488 + zIndex: 999,
489 + },
490 + {
491 + x: 300,
492 + y: 1150,
493 + text: nickname.value,
494 + fontSize: 50,
495 + color: '#333',
496 + opacity: 1,
497 + baseLine: 'middle',
498 + textAlign: 'left',
499 + lineHeight: 50,
500 + lineNum: 1,
501 + zIndex: 999,
502 + },
503 + {
504 + x: 300,
505 + y: 1220,
506 + text: '邀请你一起来活动!',
507 + fontSize: 42,
508 + color: '#8F9399',
509 + opacity: 1,
510 + baseLine: 'middle',
511 + textAlign: 'left',
512 + lineHeight: 42,
513 + lineNum: 1,
514 + zIndex: 999,
515 + }
516 + ],
517 + images: [
518 + {
519 + url: qrcode_url,
520 + width: 949,
521 + height: 108,
522 + x: 40,
523 + y: 960,
524 + zIndex: 10,
525 + },
526 + {
527 + url: qrcode_url,
528 + width: 950,
529 + height: 500,
530 + x: 40,
531 + y: 20,
532 + borderRadiusGroup: [18, 18, 0, 0],
533 + zIndex: 10,
534 + },
535 + {
536 + url: qrcode_url,
537 + width: 40,
538 + height: 40,
539 + x: 80,
540 + y: 750,
541 + borderRadius: 100,
542 + borderWidth: 0,
543 + zIndex: 10,
544 + },
545 + {
546 + url: qrcode_url,
547 + width: 35,
548 + height: 40,
549 + x: 80,
550 + y: 850,
551 + borderRadius: 100,
552 + borderWidth: 0,
553 + zIndex: 10,
554 + },
555 + {
556 + url: qrcode_url,
557 + width: 170,
558 + height: 170,
559 + x: 80,
560 + y: 1090,
561 + borderRadius: 100,
562 + borderWidth: 0,
563 + zIndex: 10,
564 + },
565 + {
566 + url: qrcode_url,
567 + width: 170,
568 + height: 170,
569 + x: 750,
570 + y: 1090,
571 + borderRadius: 100,
572 + borderWidth: 0,
573 + zIndex: 10,
574 + },
575 + ],
576 + lines: []
577 + }
578 +
579 + startDraw.value = true
580 + if (!posterPath.value) Taro.showLoading({ title: '生成海报中...' })
581 +}
582 +
583 +/**
584 + * 海报绘制成功回调
585 + */
586 +const drawSuccess = (result) => {
587 + console.log('绘制成功', result)
588 + const { tempFilePath, errMsg } = result
589 + if (errMsg === 'canvasToTempFilePath:ok') {
590 + posterPath.value = tempFilePath
591 + Taro.hideLoading()
592 + } else {
593 + Taro.hideLoading()
594 + Taro.showToast({
595 + title: '生成失败,请稍后重试',
596 + icon: 'none',
597 + duration: 2500
598 + })
599 + }
600 +}
601 +
602 +/**
603 + * 海报绘制失败回调
604 + */
605 +const drawFail = (result) => {
606 + console.log('绘制失败', result)
607 + Taro.hideLoading()
608 + Taro.showToast({
609 + title: '生成失败,请稍后重试',
610 + icon: 'none',
611 + duration: 2500
612 + })
613 +}
614 +
615 +/**
616 + * 保存海报到相册
617 + */
618 +const savePoster = () => {
619 + Taro.saveImageToPhotosAlbum({
620 + filePath: posterPath.value,
621 + success() {
622 + Taro.showToast({
623 + title: '已保存到相册',
624 + icon: 'success',
625 + duration: 2000
626 + })
627 + },
628 + fail() {
629 + Taro.showToast({
630 + title: '保存失败',
631 + icon: 'none',
632 + duration: 2000
633 + })
634 + }
635 + })
636 +}
637 +
213 // 页面挂载时检查定位授权状态 638 // 页面挂载时检查定位授权状态
214 onMounted(() => { 639 onMounted(() => {
215 checkLocationAuth() 640 checkLocationAuth()
......
1 +{
2 + "compilerOptions": {
3 + "target": "ES2018",
4 + "lib": [
5 + "ES6",
6 + "ES2017",
7 + "ES2018",
8 + "ES2019"
9 + ],
10 + "allowJs": true,
11 + "skipLibCheck": true,
12 + "esModuleInterop": true,
13 + "allowSyntheticDefaultImports": true,
14 + "strict": false,
15 + "forceConsistentCasingInFileNames": true,
16 + "module": "ESNext",
17 + "moduleResolution": "node",
18 + "resolveJsonModule": true,
19 + "isolatedModules": true,
20 + "noEmit": true,
21 + "jsx": "preserve",
22 + "baseUrl": ".",
23 + "paths": {
24 + "@/*": ["./src/*"]
25 + },
26 + "types": [
27 + "node"
28 + ]
29 + },
30 + "include": [
31 + "./src/**/*",
32 + "./types/**/*"
33 + ],
34 + "exclude": [
35 + "node_modules",
36 + "dist",
37 + "**/*.js"
38 + ]
39 +}
...\ No newline at end of file ...\ No newline at end of file