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 }
......
This diff is collapsed. Click to expand it.
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