Compare commits
23 Commits
fix/jgh/03
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10dab7ee84 | ||
|
|
3212503a64 | ||
|
|
ce1f5485fe | ||
|
|
33ab46aec0 | ||
|
|
b5081afb2c | ||
|
|
5a753e7822 | ||
|
|
66c5ea6284 | ||
| 8090d679b4 | |||
| 4965d6c40e | |||
| 56fb3ade00 | |||
| 8c6fb1190e | |||
|
|
4dc8e84f5c | ||
| b84c3bb409 | |||
| fa41842e75 | |||
| 3bbb64d58c | |||
| 58cf46e93d | |||
|
|
8004b26bd1 | ||
|
|
98baa371ee | ||
|
|
f87859da0e | ||
| d3390d5e81 | |||
|
|
883ce3c2c4 | ||
|
|
63bcf6fe86 | ||
| a68da08c85 |
20688
package-lock.json
generated
Normal file
20688
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -70,11 +70,11 @@
|
|||||||
"eslint-plugin-react": "^7.8.2",
|
"eslint-plugin-react": "^7.8.2",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.18",
|
||||||
"react-refresh": "^0.11.0",
|
"react-refresh": "^0.14.0",
|
||||||
"stylelint": "^14.4.0",
|
"stylelint": "^14.4.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.1",
|
"tsconfig-paths-webpack-plugin": "^4.0.1",
|
||||||
"typescript": "^5.1.0",
|
"typescript": "^5.1.0",
|
||||||
"webpack": "5.78.0"
|
"webpack": "5.91.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"miniprogramRoot": "dist/",
|
"miniprogramRoot": "dist/",
|
||||||
"projectname": "playBallTogether",
|
"projectname": "playBallTogether",
|
||||||
"description": "playBallTogether",
|
"description": "playBallTogether",
|
||||||
"appid": "wx815b533167eb7b53",
|
"appid": "wx915ecf6c01bea4ec",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": true,
|
"urlCheck": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
@@ -40,10 +40,21 @@
|
|||||||
"minifyWXML": true,
|
"minifyWXML": true,
|
||||||
"swc": true,
|
"swc": true,
|
||||||
"disableSWC": false,
|
"disableSWC": false,
|
||||||
"ignoreUploadUnusedFiles": true
|
"ignoreUploadUnusedFiles": true,
|
||||||
|
"compileWorklet": false,
|
||||||
|
"localPlugins": false,
|
||||||
|
"disableUseStrict": false,
|
||||||
|
"useCompilerPlugins": false,
|
||||||
|
"condition": false
|
||||||
},
|
},
|
||||||
"compileType": "miniprogram",
|
"compileType": "miniprogram",
|
||||||
"simulatorType": "wechat",
|
"simulatorType": "wechat",
|
||||||
"simulatorPluginLibVersion": {},
|
"simulatorPluginLibVersion": {},
|
||||||
"condition": {}
|
"condition": {},
|
||||||
|
"libVersion": "3.9.0",
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": [],
|
||||||
|
"include": []
|
||||||
|
},
|
||||||
|
"editorSetting": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"libVersion": "3.9.0",
|
"libVersion": "3.15.1",
|
||||||
"projectname": "playBallTogether",
|
"projectname": "mini-programs",
|
||||||
"condition": {},
|
"condition": {},
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": false,
|
"urlCheck": false,
|
||||||
|
|||||||
@@ -32,36 +32,52 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
const { timeBubbleData, gamesNum } = store;
|
const { timeBubbleData, gamesNum } = store;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 处理字典选项
|
* @description 日期排序
|
||||||
* @param dictionaryValue 字典选项
|
* @param a 日期字符串
|
||||||
* @returns 选项列表
|
* @param b 日期字符串
|
||||||
|
* @returns 日期差值
|
||||||
*/
|
*/
|
||||||
// const [selectedDates, setSelectedDates] = useState<String[]>([])
|
const sortByDate = (a: string, b: string) => {
|
||||||
|
return new Date(a).getTime() - new Date(b).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
const handleDateChange = (dates: Date[]) => {
|
const handleDateChange = (dates: Date[]) => {
|
||||||
let times: String[] = [];
|
// ================================ 日期处理 ================================
|
||||||
if (dates.length > 1) {
|
// 默认是是当前日期为开始日期,结束日期为当前日期 + 30天
|
||||||
times = [dayjs(dates[0]).format('YYYY-MM-DD'), dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')]
|
const defaultDateRange = [dayjs().format('YYYY-MM-DD'), dayjs().add(1, 'M').format('YYYY-MM-DD')];
|
||||||
onChange({
|
// 处理空数组的情况
|
||||||
'dateRange': times,
|
if (!dates.length) {
|
||||||
})
|
onChange({ dateRange: defaultDateRange });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Array.isArray(dates)) {
|
// 处理多日期范围选择(超过1个日期)
|
||||||
|
if (dates.length > 1) {
|
||||||
const currentDay = dayjs(dates[0]).format('YYYY-MM-DD');
|
const dateRange = [
|
||||||
if (filterOptions.dateRange.length === 0 || filterOptions.dateRange.length === 2) {
|
dayjs(dates[0]).format('YYYY-MM-DD'),
|
||||||
times.push(currentDay);
|
dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')
|
||||||
} else {
|
];
|
||||||
times = [...filterOptions.dateRange, currentDay].sort(
|
onChange({ dateRange });
|
||||||
(a, b) => new Date(a).getTime() - new Date(b).getTime()
|
return;
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 处理单个日期选择
|
||||||
onChange({
|
const currentFilterOptionsDateRange = Array.isArray(filterOptions?.dateRange)
|
||||||
'dateRange': times,
|
? filterOptions.dateRange
|
||||||
})
|
: defaultDateRange;
|
||||||
|
// 当前选择的日期
|
||||||
|
const currentDay = dayjs(dates?.[0]).format('YYYY-MM-DD');
|
||||||
|
// 当 dates 每次只返回单个日期时,使用已选范围判断是“第一次点”还是“第二次点”
|
||||||
|
let dateRange: string[];
|
||||||
|
if (
|
||||||
|
currentFilterOptionsDateRange.length === 2 &&
|
||||||
|
currentFilterOptionsDateRange?.[0] === currentFilterOptionsDateRange?.[1]
|
||||||
|
) {
|
||||||
|
// 已是单日,点击当前日期扩展为日期范围
|
||||||
|
dateRange = [currentFilterOptionsDateRange[0], currentDay].sort(sortByDate);
|
||||||
|
} else {
|
||||||
|
// 默认区间/已选区间/异常状态,点击当前日期统一收敛为单日
|
||||||
|
dateRange = [currentDay, currentDay];
|
||||||
|
}
|
||||||
|
onChange({ dateRange });
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOptions = (dictionaryValue: []) => {
|
const handleOptions = (dictionaryValue: []) => {
|
||||||
|
|||||||
@@ -45,10 +45,9 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
// 获取屏幕宽度,如果没有传入width则使用屏幕宽度
|
// 获取屏幕宽度,如果没有传入width则使用屏幕宽度
|
||||||
const windowWidth = Taro.getSystemInfoSync().windowWidth
|
const windowWidth = Taro.getSystemInfoSync().windowWidth
|
||||||
|
|
||||||
// 获取 DPR - 使用系统像素比确保高清显示
|
// 获取 DPR:统一与 share.ts 策略,限制上限避免内存过高
|
||||||
// const systemDpr = Taro.getSystemInfoSync().pixelRatio
|
const systemDpr = (Taro as any).getSystemInfoSync?.().pixelRatio || 1
|
||||||
const dpr = 1
|
const dpr = Math.min(Math.max(systemDpr, 1), 2)
|
||||||
// Math.min(systemDpr, 3) // 限制最大dpr为3,避免过度放大
|
|
||||||
|
|
||||||
// 2. 计算缩放比例(设备宽度 / 设计稿宽度)
|
// 2. 计算缩放比例(设备宽度 / 设计稿宽度)
|
||||||
const scale = windowWidth / designWidth
|
const scale = windowWidth / designWidth
|
||||||
@@ -105,7 +104,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
|
|
||||||
// 绘制边框
|
// 绘制边框
|
||||||
ctx.strokeStyle = borderColor
|
ctx.strokeStyle = borderColor
|
||||||
ctx.lineWidth = 1 * dpr
|
ctx.lineWidth = 1
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
// 绘制文字
|
// 绘制文字
|
||||||
@@ -145,14 +144,14 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
const drawSVGPathToCanvas = (ctx: any) => {
|
const drawSVGPathToCanvas = (ctx: any) => {
|
||||||
// 设置绘制样式
|
// 设置绘制样式
|
||||||
ctx.strokeStyle = '#00E5AD';
|
ctx.strokeStyle = '#00E5AD';
|
||||||
ctx.lineWidth = scale * 3 * dpr;
|
ctx.lineWidth = scale * 3;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
// 移动到指定位置并缩放
|
// 移动到指定位置并缩放
|
||||||
ctx.translate(scale * 200 * dpr, scale * 90 * dpr);
|
ctx.translate(scale * 200, scale * 90);
|
||||||
const scaleValue = 0.8
|
const scaleValue = 0.8
|
||||||
ctx.scale(scaleValue, scaleValue);
|
ctx.scale(scaleValue, scaleValue);
|
||||||
|
|
||||||
@@ -382,43 +381,39 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
setIsDrawing(true)
|
setIsDrawing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置Canvas的实际尺寸(使用dpr确保高清显示)
|
// 统一坐标系:先用物理像素清空,再缩放到逻辑坐标绘制
|
||||||
const canvasWidthPx = canvasWidth * dpr
|
const canvasWidthPx = canvasWidth * dpr
|
||||||
const canvasHeightPx = canvasHeight * dpr
|
const canvasHeightPx = canvasHeight * dpr
|
||||||
|
if (typeof ctx.setTransform === 'function') {
|
||||||
// 清空画布
|
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
|
}
|
||||||
ctx.clearRect(0, 0, canvasWidthPx, canvasHeightPx)
|
ctx.clearRect(0, 0, canvasWidthPx, canvasHeightPx)
|
||||||
|
ctx.save()
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
console.log('画布已清空')
|
console.log('画布已清空')
|
||||||
|
|
||||||
// 如果dpr大于2,进行缩放处理以避免内容过大
|
|
||||||
if (dpr > 2) {
|
|
||||||
const scale = 2 / dpr
|
|
||||||
ctx.scale(scale, scale)
|
|
||||||
console.log('应用缩放:', scale)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制背景 - 渐变色 已完成
|
// 绘制背景 - 渐变色 已完成
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeightPx)
|
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight)
|
||||||
gradient.addColorStop(0, '#BFFFEF')
|
gradient.addColorStop(0, '#BFFFEF')
|
||||||
gradient.addColorStop(1, '#F2FFFC')
|
gradient.addColorStop(1, '#F2FFFC')
|
||||||
ctx.fillStyle = gradient
|
ctx.fillStyle = gradient
|
||||||
ctx.fillRect(0, 0, canvasWidthPx, canvasHeightPx)
|
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
|
||||||
console.log('背景绘制完成')
|
console.log('背景绘制完成')
|
||||||
|
|
||||||
// 绘制背景条纹 已完成
|
// 绘制背景条纹 已完成
|
||||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.03)'
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.03)'
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2
|
||||||
for (let i = 0; i < canvasWidthPx; i += 4) {
|
for (let i = 0; i < canvasWidth; i += 4) {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(i, 0)
|
ctx.moveTo(i, 0)
|
||||||
ctx.lineTo(i, canvasHeightPx)
|
ctx.lineTo(i, canvasHeight)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制用户头像(左上角) 已完成
|
// 绘制用户头像(左上角) 已完成
|
||||||
const avatarSize = scale * 32 * dpr // 32px * dpr
|
const avatarSize = scale * 32
|
||||||
const avatarX = scale * 35 * dpr // 距离左侧35px
|
const avatarX = scale * 35
|
||||||
const avatarY = scale * 35 * dpr // 距离顶部35px
|
const avatarY = scale * 35
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const avatarPath = await loadImage(data.userAvatar, canvasNode)
|
const avatarPath = await loadImage(data.userAvatar, canvasNode)
|
||||||
@@ -438,23 +433,23 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 绘制用户昵称 已完成
|
// 绘制用户昵称 已完成
|
||||||
const nicknameX = avatarX + avatarSize + 8 * dpr // 距离头像8px
|
const nicknameX = avatarX + avatarSize + scale * 8
|
||||||
const nicknameY = avatarY + (avatarSize - 18 * dpr) / 2 // 与头像水平居中对齐
|
const nicknameY = avatarY + (avatarSize - scale * 18) / 2
|
||||||
const nicknameFontSize = scale * 18 * dpr
|
const nicknameFontSize = scale * 18
|
||||||
// drawText(ctx, data.userNickname, nicknameX, nicknameY, 200 * dpr, nicknameFontSize, '#000000', true, 'Noto Sans SC')
|
// drawText(ctx, data.userNickname, nicknameX, nicknameY, 200 * dpr, nicknameFontSize, '#000000', true, 'Noto Sans SC')
|
||||||
drawBoldText(ctx, data.userNickname, nicknameX, nicknameY, nicknameFontSize, '#000000', 'Noto Sans SC', '900')
|
drawBoldText(ctx, data.userNickname, nicknameX, nicknameY, nicknameFontSize, '#000000', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 绘制"邀你加入球局"文案
|
// 绘制"邀你加入球局"文案
|
||||||
const inviteX = scale * 35 * dpr // 距离画布左侧35px
|
const inviteX = scale * 35
|
||||||
const inviteY = scale * 100 * dpr // 距离画布顶部79px
|
const inviteY = scale * 100
|
||||||
const inviteFontSize = scale * 44 * dpr
|
const inviteFontSize = scale * 44
|
||||||
|
|
||||||
// 绘制"邀你加入"
|
// 绘制"邀你加入"
|
||||||
drawBoldText(ctx, '邀你加入', inviteX, inviteY, inviteFontSize, '#000000', 'Noto Sans SC', '900')
|
drawBoldText(ctx, '邀你加入', inviteX, inviteY, inviteFontSize, '#000000', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 绘制"球局"特殊样式
|
// 绘制"球局"特殊样式
|
||||||
const qiuJuX = inviteX + ctx.measureText('邀你加入').width + 4 * dpr
|
const qiuJuX = inviteX + ctx.measureText('邀你加入').width + scale * 4
|
||||||
const qiuJuFontSize = scale * 44 * dpr
|
const qiuJuFontSize = scale * 44
|
||||||
drawBoldText(ctx, '球局', qiuJuX, inviteY, qiuJuFontSize, '#00E5AD', 'Noto Sans SC', '900')
|
drawBoldText(ctx, '球局', qiuJuX, inviteY, qiuJuFontSize, '#00E5AD', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 测试绘制网络图片
|
// 测试绘制网络图片
|
||||||
@@ -462,12 +457,12 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
|
|
||||||
// 绘制球员图片(右上角)已完成
|
// 绘制球员图片(右上角)已完成
|
||||||
let venueBaseConfig = {
|
let venueBaseConfig = {
|
||||||
venueImgX: scale * 340 * dpr,
|
venueImgX: scale * 340,
|
||||||
venueImgY: scale * 35 * dpr,
|
venueImgY: scale * 35,
|
||||||
rotation: scale * -8, // 旋转-8度
|
rotation: scale * -8, // 旋转-8度
|
||||||
venueImgSize: scale * 124 * dpr,
|
venueImgSize: scale * 124,
|
||||||
borderRadius: scale * 24 * dpr,
|
borderRadius: scale * 24,
|
||||||
padding: scale * 4 * dpr,
|
padding: scale * 4,
|
||||||
venueImage: data.venueImages?.[0]
|
venueImage: data.venueImages?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,8 +471,8 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
const venueBackConfig = {
|
const venueBackConfig = {
|
||||||
...venueBaseConfig,
|
...venueBaseConfig,
|
||||||
venueImage: data.venueImages?.[1],
|
venueImage: data.venueImages?.[1],
|
||||||
venueImgX: scale * 400 * dpr,
|
venueImgX: scale * 400,
|
||||||
venueImgY: scale * 35 * dpr,
|
venueImgY: scale * 35,
|
||||||
rotation: scale * -10, // 旋转-10度
|
rotation: scale * -10, // 旋转-10度
|
||||||
}
|
}
|
||||||
await drawVenueImages(ctx, venueBackConfig, canvasNode)
|
await drawVenueImages(ctx, venueBackConfig, canvasNode)
|
||||||
@@ -512,11 +507,11 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
// 绘制"单打"标签
|
// 绘制"单打"标签
|
||||||
const danDaX = scale * 100
|
const danDaX = scale * 100
|
||||||
const danDaY = scale * 196
|
const danDaY = scale * 196
|
||||||
const danDaHeight = scale * 40 * dpr
|
const danDaHeight = scale * 40
|
||||||
const danDaRadius = scale * 20 * dpr
|
const danDaRadius = scale * 20
|
||||||
const danDaFontSize = scale * 22 * dpr
|
const danDaFontSize = scale * 22
|
||||||
// 根据内容动态计算标签宽度(左右内边距)
|
// 根据内容动态计算标签宽度(左右内边距)
|
||||||
const danDaPaddingX = scale * 16 * dpr
|
const danDaPaddingX = scale * 16
|
||||||
setFont2D(ctx, danDaFontSize)
|
setFont2D(ctx, danDaFontSize)
|
||||||
const danDaTextWidth = ctx.measureText(data.gameType).width
|
const danDaTextWidth = ctx.measureText(data.gameType).width
|
||||||
const danDaWidth = danDaTextWidth + danDaPaddingX * 2
|
const danDaWidth = danDaTextWidth + danDaPaddingX * 2
|
||||||
@@ -527,11 +522,11 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
const labelGap = scale * 16 // 两个标签之间的间距(不乘 dpr,保持视觉间距)
|
const labelGap = scale * 16 // 两个标签之间的间距(不乘 dpr,保持视觉间距)
|
||||||
const skillX = danDaX + danDaWidth + labelGap
|
const skillX = danDaX + danDaWidth + labelGap
|
||||||
const skillY = scale * 196
|
const skillY = scale * 196
|
||||||
const skillHeight = scale * 40 * dpr
|
const skillHeight = scale * 40
|
||||||
const skillRadius = scale * 20 * dpr
|
const skillRadius = scale * 20
|
||||||
const skillFontSize = scale * 22 * dpr
|
const skillFontSize = scale * 22
|
||||||
// 根据内容动态计算技能标签宽度
|
// 根据内容动态计算技能标签宽度
|
||||||
const skillPaddingX = scale * 20 * dpr
|
const skillPaddingX = scale * 20
|
||||||
setFont2D(ctx, skillFontSize)
|
setFont2D(ctx, skillFontSize)
|
||||||
const skillTextWidth = ctx.measureText(data.skillLevel).width
|
const skillTextWidth = ctx.measureText(data.skillLevel).width
|
||||||
const skillWidth = skillTextWidth + skillPaddingX * 2
|
const skillWidth = skillTextWidth + skillPaddingX * 2
|
||||||
@@ -541,7 +536,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
// 绘制日期时间
|
// 绘制日期时间
|
||||||
const dateX = danDaX
|
const dateX = danDaX
|
||||||
const timeInfoY = infoStartY + infoSpacing
|
const timeInfoY = infoStartY + infoSpacing
|
||||||
const timeInfoFontSize = scale * 24 * dpr
|
const timeInfoFontSize = scale * 24
|
||||||
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`, canvasNode)
|
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`, canvasNode)
|
||||||
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
@@ -549,17 +544,18 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
drawBoldText(ctx, data.gameDate, dateX, timeInfoY + 8, timeInfoFontSize, '#00E5AD')
|
drawBoldText(ctx, data.gameDate, dateX, timeInfoY + 8, timeInfoFontSize, '#00E5AD')
|
||||||
|
|
||||||
// 绘制时间(黑色)
|
// 绘制时间(黑色)
|
||||||
const timeX = textX + ctx.measureText(data.gameDate).width + 10 * dpr
|
const timeX = textX + ctx.measureText(data.gameDate).width + scale * 10
|
||||||
// drawText(ctx, data.gameTime, timeX, timeInfoY + 8, 300, timeInfoFontSize, '#000000')
|
// drawText(ctx, data.gameTime, timeX, timeInfoY + 8, 300, timeInfoFontSize, '#000000')
|
||||||
drawBoldText(ctx, data.gameTime, timeX, timeInfoY + 8, timeInfoFontSize, '#000000')
|
drawBoldText(ctx, data.gameTime, timeX, timeInfoY + 8, timeInfoFontSize, '#000000')
|
||||||
|
|
||||||
// 绘制地点
|
// 绘制地点
|
||||||
const locationInfoY = infoStartY + infoSpacing * 2
|
const locationInfoY = infoStartY + infoSpacing * 2
|
||||||
const locationFontSize = scale * 22 * dpr
|
const locationFontSize = scale * 22
|
||||||
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`, canvasNode)
|
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`, canvasNode)
|
||||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||||
|
|
||||||
|
ctx.restore()
|
||||||
// 绘制完成,调用draw方法
|
// 绘制完成,调用draw方法
|
||||||
console.log('开始调用ctx.draw()')
|
console.log('开始调用ctx.draw()')
|
||||||
const doExport = () => {
|
const doExport = () => {
|
||||||
@@ -649,12 +645,9 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
if (data && data.node) {
|
if (data && data.node) {
|
||||||
const canvas = data.node
|
const canvas = data.node
|
||||||
const context = canvas.getContext('2d')
|
const context = canvas.getContext('2d')
|
||||||
// DPR 缩放,提升清晰度(当前 dpr = 1 也可正常显示)
|
// 仅设置物理像素尺寸,绘制阶段统一在 drawShareCard 中做 ctx.scale(dpr, dpr)
|
||||||
const sys = (Taro as any).getSystemInfoSync?.() || {}
|
canvas.width = canvasWidth * dpr
|
||||||
const ratio = sys.pixelRatio || 1
|
canvas.height = canvasHeight * dpr
|
||||||
canvas.width = canvasWidth * ratio
|
|
||||||
canvas.height = canvasHeight * ratio
|
|
||||||
context.scale(ratio, ratio)
|
|
||||||
setCanvasNode(canvas)
|
setCanvasNode(canvas)
|
||||||
setCtx2d(context)
|
setCtx2d(context)
|
||||||
setIs2dCtx(true)
|
setIs2dCtx(true)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface UploadFromWxProps {
|
|||||||
|
|
||||||
async function convert_to_jpg_and_compress(
|
async function convert_to_jpg_and_compress(
|
||||||
src: string,
|
src: string,
|
||||||
{ width, height }
|
{ width, height, quality }: { width: number; height: number; quality: number }
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const canvas = Taro.createOffscreenCanvas({ type: "2d", width, height });
|
const canvas = Taro.createOffscreenCanvas({ type: "2d", width, height });
|
||||||
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
|
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
|
||||||
@@ -33,22 +33,54 @@ async function convert_to_jpg_and_compress(
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.canvasToTempFilePath({
|
Taro.canvasToTempFilePath({
|
||||||
canvas: canvas as unknown as Taro.Canvas,
|
canvas: canvas as unknown as Taro.Canvas,
|
||||||
fileType: "png",
|
fileType: "jpg",
|
||||||
quality: 0.7,
|
quality,
|
||||||
success: (res) => resolve(res.tempFilePath),
|
success: (res) => resolve(res.tempFilePath),
|
||||||
fail: reject,
|
fail: reject,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFileSize(path: string): Promise<number> {
|
||||||
|
const fs = (Taro as any).getFileSystemManager();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.stat({
|
||||||
|
path,
|
||||||
|
success: (res) => resolve(res.stats.size),
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function compressImage(files) {
|
async function compressImage(files) {
|
||||||
const res: string[] = [];
|
const res: string[] = [];
|
||||||
|
const qualityList = [0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const compressed_image = await convert_to_jpg_and_compress(file.path, {
|
// 小图且体积已在目标范围内时,直接上传原图,避免二次压缩变糊
|
||||||
width: file.width,
|
if (
|
||||||
height: file.height,
|
file.size <= TARGET_IMAGE_SIZE &&
|
||||||
});
|
file.width <= IMAGE_MAX_SIZE.width &&
|
||||||
res.push(compressed_image);
|
file.height <= IMAGE_MAX_SIZE.height
|
||||||
|
) {
|
||||||
|
res.push(file.path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bestImage = file.path;
|
||||||
|
for (const quality of qualityList) {
|
||||||
|
const compressedImage = await convert_to_jpg_and_compress(file.path, {
|
||||||
|
width: file.width,
|
||||||
|
height: file.height,
|
||||||
|
quality,
|
||||||
|
});
|
||||||
|
const compressedSize = await getFileSize(compressedImage);
|
||||||
|
bestImage = compressedImage;
|
||||||
|
if (compressedSize <= TARGET_IMAGE_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push(bestImage);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -58,6 +90,8 @@ const IMAGE_MAX_SIZE = {
|
|||||||
width: 1080,
|
width: 1080,
|
||||||
height: 720,
|
height: 720,
|
||||||
};
|
};
|
||||||
|
// 压缩目标体积(约 300KB)
|
||||||
|
const TARGET_IMAGE_SIZE = 800 * 1024;
|
||||||
|
|
||||||
// 标准长宽比,判断标准
|
// 标准长宽比,判断标准
|
||||||
const STANDARD_ASPECT_RATIO = IMAGE_MAX_SIZE.width / IMAGE_MAX_SIZE.height;
|
const STANDARD_ASPECT_RATIO = IMAGE_MAX_SIZE.width / IMAGE_MAX_SIZE.height;
|
||||||
|
|||||||
@@ -1,41 +1,13 @@
|
|||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import { calculateDistance } from "@/utils";
|
import { calculateDistance, genGameLength } from "@/utils";
|
||||||
import { View, Image, Text, Map } from "@tarojs/components";
|
import { View, Image, Text, Map } from "@tarojs/components";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
dayjs.locale("zh-cn");
|
dayjs.locale("zh-cn");
|
||||||
|
|
||||||
function genGameLength(startTime: Dayjs, endTime: Dayjs) {
|
|
||||||
if (!startTime || !endTime) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const totalMinutes = endTime.diff(startTime, "minute");
|
|
||||||
const totalHours = totalMinutes / 60;
|
|
||||||
|
|
||||||
if (totalHours >= 24) {
|
|
||||||
const days = Math.floor(totalHours / 24);
|
|
||||||
const remainingHours = totalHours % 24;
|
|
||||||
|
|
||||||
if (remainingHours === 0) {
|
|
||||||
return `${days}天`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保留一位小数
|
|
||||||
const displayHours = parseFloat(remainingHours.toFixed(1));
|
|
||||||
return `${days}天${displayHours}小时`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是整数小时,不显示小数点
|
|
||||||
if (Number.isInteger(totalHours)) {
|
|
||||||
return `${totalHours}小时`;
|
|
||||||
}
|
|
||||||
// 保留一位小数,去除末尾的0
|
|
||||||
return `${parseFloat(totalHours.toFixed(1))}小时`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function genGameRange(startTime: Dayjs, endTime: Dayjs) {
|
function genGameRange(startTime: Dayjs, endTime: Dayjs) {
|
||||||
if (!startTime || !endTime) {
|
if (!startTime || !endTime) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -177,23 +177,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 24px; /* 150% */
|
line-height: 24px; /* 150% */
|
||||||
|
|
||||||
&-arrow {
|
&-text {
|
||||||
width: 12px;
|
flex: 1;
|
||||||
height: 12px;
|
min-width: 0;
|
||||||
}
|
overflow: hidden;
|
||||||
}
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
flex: 0 0 12px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-time-range {
|
&-time-range {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ export default function OrganizerInfo(props) {
|
|||||||
await LoginService.followUser(id);
|
await LoginService.followUser(id);
|
||||||
}
|
}
|
||||||
onUpdateUserInfo();
|
onUpdateUserInfo();
|
||||||
Taro.showToast({
|
(Taro as any).showToast({
|
||||||
title: `${nickname} ${follow ? "已取消关注" : "已关注"}`,
|
title: `${nickname} ${follow ? "已取消关注" : "已关注"}`,
|
||||||
icon: "success",
|
icon: "success",
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Taro.showToast({
|
(Taro as any).showToast({
|
||||||
title: `${nickname} ${follow ? "取消关注失败" : "关注失败"}`,
|
title: `${nickname} ${follow ? "取消关注失败" : "关注失败"}`,
|
||||||
icon: "error",
|
icon: "error",
|
||||||
});
|
});
|
||||||
@@ -193,13 +193,17 @@ export default function OrganizerInfo(props) {
|
|||||||
className={styles["recommend-games-list-item"]}
|
className={styles["recommend-games-list-item"]}
|
||||||
onClick={handleViewGame.bind(null, game.id)}
|
onClick={handleViewGame.bind(null, game.id)}
|
||||||
>
|
>
|
||||||
{/* game title */}
|
{/* game title */}
|
||||||
<View className={styles["recommend-games-list-item-title"]}>
|
<View className={styles["recommend-games-list-item-title"]}>
|
||||||
<Text>{game.title}</Text>
|
<Text
|
||||||
<Image
|
className={styles["recommend-games-list-item-title-text"]}
|
||||||
className={
|
>
|
||||||
styles["recommend-games-list-item-title-arrow"]
|
{game.title}
|
||||||
}
|
</Text>
|
||||||
|
<Image
|
||||||
|
className={
|
||||||
|
styles["recommend-games-list-item-title-arrow"]
|
||||||
|
}
|
||||||
src={img.ICON_DETAIL_ARROW_RIGHT}
|
src={img.ICON_DETAIL_ARROW_RIGHT}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ export default function Participants(props) {
|
|||||||
<Text
|
<Text
|
||||||
className={styles["participants-list-item-level"]}
|
className={styles["participants-list-item-level"]}
|
||||||
>
|
>
|
||||||
{displayNtrp}
|
NTRP {displayNtrp}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles["participants-list-item-role"]}>
|
<Text className={styles["participants-list-item-role"]}>
|
||||||
{role}
|
{role}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { forwardRef, useState, useEffect, useImperativeHandle } from "react";
|
import { forwardRef, useState, useEffect, useImperativeHandle } from "react";
|
||||||
import { View, Button, Image, Text } from "@tarojs/components";
|
import { View, Button, Image, Text } from "@tarojs/components";
|
||||||
import Taro, { useShareAppMessage } from "@tarojs/taro";
|
import Taro, { useShareAppMessage } from "@tarojs/taro";
|
||||||
import dayjs from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import { generateShareImage } from "@/utils";
|
import { generateShareImage } from "@/utils";
|
||||||
@@ -12,7 +12,12 @@ import WechatLogo from "@/static/detail/wechat_icon.svg";
|
|||||||
// import WechatTimeline from "@/static/detail/wechat_timeline.svg";
|
// import WechatTimeline from "@/static/detail/wechat_timeline.svg";
|
||||||
import LinkIcon from "@/static/detail/link.svg";
|
import LinkIcon from "@/static/detail/link.svg";
|
||||||
import CrossIcon from "@/static/detail/cross.svg";
|
import CrossIcon from "@/static/detail/cross.svg";
|
||||||
import { genNTRPRequirementText, navto } from "@/utils/helper";
|
import {
|
||||||
|
genNTRPRequirementText,
|
||||||
|
navto,
|
||||||
|
genGameLength,
|
||||||
|
formatGameStartTime,
|
||||||
|
} from "@/utils/helper";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import { useUserActions } from "@/store/userStore";
|
import { useUserActions } from "@/store/userStore";
|
||||||
import { OSS_BASE } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
@@ -28,7 +33,19 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
const [shareImageUrl, setShareImageUrl] = useState("");
|
const [shareImageUrl, setShareImageUrl] = useState("");
|
||||||
const { fetchUserInfo } = useUserActions();
|
const { fetchUserInfo } = useUserActions();
|
||||||
|
|
||||||
|
async function ensureUserInfo() {
|
||||||
|
if (userInfo?.avatar_url && userInfo?.nickname) {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
const fetchedUserInfo = await fetchUserInfo();
|
||||||
|
return {
|
||||||
|
avatar_url: fetchedUserInfo?.avatar_url || userInfo?.avatar_url || "",
|
||||||
|
nickname: fetchedUserInfo?.nickname || userInfo?.nickname || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const publishFlag = from === "publish";
|
const publishFlag = from === "publish";
|
||||||
|
|
||||||
// const posterRef = useRef();
|
// const posterRef = useRef();
|
||||||
const { max_participants, participant_count } = detail || {};
|
const { max_participants, participant_count } = detail || {};
|
||||||
|
|
||||||
@@ -50,6 +67,16 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
withShareTicket: false, // 是否需要返回 shareTicket
|
withShareTicket: false, // 是否需要返回 shareTicket
|
||||||
isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版)
|
isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版)
|
||||||
activityId: res.data.activity_id, // 动态消息的活动 id
|
activityId: res.data.activity_id, // 动态消息的活动 id
|
||||||
|
templateInfo: {
|
||||||
|
parameterList: [
|
||||||
|
{
|
||||||
|
name: "member_count",
|
||||||
|
value: (participant_count ?? 0).toString(),
|
||||||
|
},
|
||||||
|
{ name: "room_limit", value: (max_participants ?? 0).toString() },
|
||||||
|
],
|
||||||
|
templateId: "666F374D69D16C932E45D7E7D9F10CEF6177F5F5",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -85,28 +112,34 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
const startTime = dayjs(start_time);
|
const startTime = dayjs(start_time);
|
||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
const gameLength = genGameLength(startTime, endTime);
|
||||||
console.log(userInfo, "userInfo");
|
const currentUserInfo = await ensureUserInfo();
|
||||||
const url = await generateShareImage({
|
try {
|
||||||
userAvatar: userInfo.avatar_url,
|
const url = await generateShareImage({
|
||||||
userNickname: userInfo.nickname,
|
userAvatar: currentUserInfo.avatar_url,
|
||||||
gameType: play_type,
|
userNickname: currentUserInfo.nickname,
|
||||||
skillLevel: `NTRP ${genNTRPRequirementText(
|
gameType: play_type,
|
||||||
skill_level_min,
|
skillLevel: `NTRP ${genNTRPRequirementText(
|
||||||
skill_level_max,
|
skill_level_min,
|
||||||
)}`,
|
skill_level_max,
|
||||||
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
)}`,
|
||||||
gameTime: `${startTime.format("ah")}点 ${gameLength}`,
|
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||||
venueName: location_name,
|
gameTime: `${formatGameStartTime(startTime)} ${gameLength}`,
|
||||||
venueImages: image_list ? image_list : [],
|
venueName: location_name,
|
||||||
});
|
venueImages: image_list ? image_list : [],
|
||||||
return url;
|
});
|
||||||
|
if (!url) {
|
||||||
|
throw new Error("生成分享图片失败,URL 为空");
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("生成分享卡片失败", e);
|
||||||
|
return `${OSS_BASE}/system/game_dou_di_tu.png`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useShareAppMessage(async (res) => {
|
useShareAppMessage(async () => {
|
||||||
await changeMessageType();
|
const url = shareImageUrl || (await generateShareImageUrl());
|
||||||
const url = await generateShareImageUrl();
|
|
||||||
// console.log(res, "res");
|
|
||||||
return {
|
return {
|
||||||
title: detail.title,
|
title: detail.title,
|
||||||
imageUrl: url,
|
imageUrl: url,
|
||||||
@@ -128,12 +161,12 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
} = detail || {};
|
} = detail || {};
|
||||||
// 先等待静默登录完成
|
// 先等待静默登录完成
|
||||||
await waitForAuthInit();
|
await waitForAuthInit();
|
||||||
const userInfo = await fetchUserInfo();
|
const currentUserInfo = await ensureUserInfo();
|
||||||
const { avatar_url, nickname } = userInfo;
|
const { avatar_url, nickname } = currentUserInfo;
|
||||||
const startTime = dayjs(start_time);
|
const startTime = dayjs(start_time);
|
||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
const game_length = genGameLength(startTime, endTime);
|
||||||
let qrCodeUrl = "";
|
let qrCodeUrl = "";
|
||||||
try {
|
try {
|
||||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||||
@@ -161,7 +194,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
title,
|
title,
|
||||||
locationName: location_name,
|
locationName: location_name,
|
||||||
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
time: `${formatGameStartTime(startTime)} ${game_length}`,
|
||||||
qrCodeUrl,
|
qrCodeUrl,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ function isFull(counts) {
|
|||||||
} = counts;
|
} = counts;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
max_players === current_players &&
|
current_players >= max_players &&
|
||||||
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
|
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else if (
|
} else if (
|
||||||
max_players === current_players &&
|
current_players >= max_players &&
|
||||||
is_substitute_supported === IsSubstituteSupported.SUPPORT
|
is_substitute_supported === IsSubstituteSupported.SUPPORT
|
||||||
) {
|
) {
|
||||||
return max_substitute_players === current_substitute_count;
|
return max_substitute_players === current_substitute_count;
|
||||||
@@ -45,7 +45,7 @@ function RmbIcon() {
|
|||||||
function matchNtrpRequestment(
|
function matchNtrpRequestment(
|
||||||
target?: string,
|
target?: string,
|
||||||
min?: string,
|
min?: string,
|
||||||
max?: string
|
max?: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
// 目标值为空或 undefined
|
// 目标值为空或 undefined
|
||||||
if (!target?.trim()) return true;
|
if (!target?.trim()) return true;
|
||||||
@@ -123,7 +123,7 @@ export default function StickyButton(props) {
|
|||||||
|
|
||||||
Taro.navigateTo({
|
Taro.navigateTo({
|
||||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||||
fullPath
|
fullPath,
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ export default function StickyButton(props) {
|
|||||||
const matchNtrpReq = matchNtrpRequestment(
|
const matchNtrpReq = matchNtrpRequestment(
|
||||||
ntrp_level,
|
ntrp_level,
|
||||||
skill_level_min,
|
skill_level_min,
|
||||||
skill_level_max
|
skill_level_max,
|
||||||
);
|
);
|
||||||
|
|
||||||
const gameManageRef = useRef();
|
const gameManageRef = useRef();
|
||||||
@@ -173,7 +173,7 @@ export default function StickyButton(props) {
|
|||||||
}, [getCommentCount]);
|
}, [getCommentCount]);
|
||||||
|
|
||||||
function generateTextAndAction(
|
function generateTextAndAction(
|
||||||
user_action_status: null | { [key: string]: boolean }
|
user_action_status: null | { [key: string]: boolean },
|
||||||
):
|
):
|
||||||
| undefined
|
| undefined
|
||||||
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
||||||
@@ -271,7 +271,7 @@ export default function StickyButton(props) {
|
|||||||
const res = await OrderService.getUnpaidOrder(id);
|
const res = await OrderService.getUnpaidOrder(id);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
navto(
|
navto(
|
||||||
`/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`
|
`/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -387,7 +387,7 @@ export default function StickyButton(props) {
|
|||||||
<View
|
<View
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles["detail-main-action"],
|
styles["detail-main-action"],
|
||||||
available ? "" : styles.disabled
|
available ? "" : styles.disabled,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ export default function VenueInfo(props) {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const {
|
const {
|
||||||
venue_description,
|
venue_description,
|
||||||
|
court_type,
|
||||||
venue_description_tag = [],
|
venue_description_tag = [],
|
||||||
venue_image_list = [],
|
venue_image_list = [],
|
||||||
} = detail;
|
} = detail;
|
||||||
|
const venue_tags = [court_type, ...venue_description_tag].filter(Boolean);
|
||||||
|
|
||||||
// 统一为 URL 数组:接口可能是 { id, url }[] 或 string[]
|
// 统一为 URL 数组:接口可能是 { id, url }[] 或 string[]
|
||||||
const screenshot_urls = (venue_image_list || []).map((item) =>
|
const screenshot_urls = (venue_image_list || []).map((item) =>
|
||||||
@@ -61,7 +63,7 @@ export default function VenueInfo(props) {
|
|||||||
<View className={styles["venue-detail-content"]}>
|
<View className={styles["venue-detail-content"]}>
|
||||||
{/* venue detail tags */}
|
{/* venue detail tags */}
|
||||||
<View className={styles["venue-detail-content-tags"]}>
|
<View className={styles["venue-detail-content-tags"]}>
|
||||||
{insertDotInTags(venue_description_tag).map((tag, index) => (
|
{insertDotInTags(venue_tags).map((tag, index) => (
|
||||||
<View
|
<View
|
||||||
key={index}
|
key={index}
|
||||||
className={styles["venue-detail-content-tags-tag"]}
|
className={styles["venue-detail-content-tags-tag"]}
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ import WechatLogo from "@/static/detail/wechat_icon.svg";
|
|||||||
import WechatTimeline from "@/static/detail/wechat_timeline.svg";
|
import WechatTimeline from "@/static/detail/wechat_timeline.svg";
|
||||||
import { useUserActions } from "@/store/userStore";
|
import { useUserActions } from "@/store/userStore";
|
||||||
import { DayOfWeekMap } from "../detail/config";
|
import { DayOfWeekMap } from "../detail/config";
|
||||||
import { genNTRPRequirementText } from "@/utils/helper";
|
import {
|
||||||
|
genNTRPRequirementText,
|
||||||
|
genGameLength,
|
||||||
|
formatGameStartTime,
|
||||||
|
} from "@/utils/helper";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import { OSS_BASE } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
@@ -53,7 +57,7 @@ function SharePoster(props) {
|
|||||||
const startTime = dayjs(start_time);
|
const startTime = dayjs(start_time);
|
||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
const gameLength = genGameLength(startTime, endTime);
|
||||||
Taro.showLoading({ title: "生成中..." });
|
Taro.showLoading({ title: "生成中..." });
|
||||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||||
page: "game_pages/detail/index",
|
page: "game_pages/detail/index",
|
||||||
@@ -77,7 +81,7 @@ function SharePoster(props) {
|
|||||||
title,
|
title,
|
||||||
locationName: location_name,
|
locationName: location_name,
|
||||||
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
time: `${formatGameStartTime(startTime)} ${gameLength}`,
|
||||||
qrCodeUrl,
|
qrCodeUrl,
|
||||||
});
|
});
|
||||||
Taro.hideLoading();
|
Taro.hideLoading();
|
||||||
|
|||||||
@@ -22,3 +22,135 @@ export const DECLAIMER = `
|
|||||||
发起人临时失联/爽约;发起人恶意删除队员,GO!支持全额退款
|
发起人临时失联/爽约;发起人恶意删除队员,GO!支持全额退款
|
||||||
参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。
|
参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
interface RegInsChildTipType {
|
||||||
|
text: string
|
||||||
|
strong?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegInsChildTableType {
|
||||||
|
refundApplicationTime: string
|
||||||
|
participantRefundableAmount: string
|
||||||
|
liquidatedDamages: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegInsChildType {
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
table?: RegInsChildTableType[]
|
||||||
|
tips: RegInsChildTipType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegInsType {
|
||||||
|
title: string,
|
||||||
|
desc: string,
|
||||||
|
children: RegInsChildType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistrationInstructions: RegInsType = {
|
||||||
|
title: '报名须知',
|
||||||
|
desc: '请在确认支付前仔细阅读以下内容,完成支付即视为您已同意本须知全部内容。',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: '一、退款规则',
|
||||||
|
desc: '',
|
||||||
|
table: [
|
||||||
|
{
|
||||||
|
refundApplicationTime: '申请退款时间',
|
||||||
|
participantRefundableAmount: '参与者可退',
|
||||||
|
liquidatedDamages: '违约金',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refundApplicationTime: '活动开始前24小时',
|
||||||
|
participantRefundableAmount: '报名费 100%',
|
||||||
|
liquidatedDamages: '无',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refundApplicationTime: '活动开始前12~24小时',
|
||||||
|
participantRefundableAmount: '报名费 50%',
|
||||||
|
liquidatedDamages: '报名费 50%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refundApplicationTime: '活动开始前12小时内',
|
||||||
|
participantRefundableAmount: '报名费 20%',
|
||||||
|
liquidatedDamages: '报名费 80%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refundApplicationTime: '未申请 / 直接缺席',
|
||||||
|
participantRefundableAmount: '0%',
|
||||||
|
liquidatedDamages: '视为放弃,全归组织者',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
text: '以上时间节点以提交申请时间为准,非活动开始时间;',
|
||||||
|
strong: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '退款申请入口:活动详情页 > 退出活动',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '退款原路退回至微信支付账户,到账时间 1~5 个工作日;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '违约金由组织者(95%)与平台(5%)按比例分配。其中组织者所得部分用于补偿其因人数临时变动产生的场地费损失,平台所得部分用于覆盖违约事务的处理成本;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '未申请退款直接缺席的,报名费于活动结束后自动结算给组织者,平台不参与分配',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '二、特殊情形退款',
|
||||||
|
desc: '以下特殊情形可申请全额退款,需联系客服并提供相关证明材料:',
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
text: '活动当天遭遇极端恶劣天气(台风、暴雨红色预警等);',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '球场临时关闭或其他不可抗力导致活动无法进行;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '参与者本人突发疾病或意外(需提供医院证明)。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '三、活动取消规则',
|
||||||
|
desc: '',
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
text: '到达活动开始时间时,报名人数仍未达到最低成局人数,活动自动取消,已付款参与者全额退款;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '组织者主动取消活动,所有已付款参与者全额退款;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '以上退款均由系统自动处理,无需申请。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '四、免责声明',
|
||||||
|
desc: '',
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
text: '本平台仅为网球约球信息撮合平台,不直接提供场地或运动服务,不对活动中的人身安全及财物损失承担责任;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '网球运动存在固有运动风险,请在参与前评估自身身体状况,患有心脏病、高血压等基础疾病者请在医生许可下参与;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '平台强烈建议参与者购买运动意外保险;',
|
||||||
|
strong: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '因组织者或场地方原因导致活动变更或取消,平台将协助处理但不承担连带责任;',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '本平台不对因网络故障、系统维护或不可抗力导致的服务中断承担责任。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|||||||
@@ -424,6 +424,7 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.orderNo {
|
.orderNo {
|
||||||
@@ -544,21 +545,160 @@
|
|||||||
.time {
|
.time {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rule {
|
// .rule {
|
||||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
// border-left: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.refundTip {
|
||||||
|
margin-top: 16px;
|
||||||
|
color: rgba(60, 60, 67, 0.6);
|
||||||
|
text-align: center;
|
||||||
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.declaimer {
|
.disclaimer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
gap: 16px;
|
||||||
gap: 8px;
|
margin-top: 16px;
|
||||||
padding-bottom: 100px;
|
|
||||||
|
.disclaimerTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimerDesc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimerSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionDesc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// gap: 8px;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.tableRow {
|
||||||
|
display: flex;
|
||||||
|
min-height: 44px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
.tableCell {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableCell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
line-height: 18px;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
flex: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipText {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipsList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 8px 0;
|
||||||
|
|
||||||
|
.tipItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "•";
|
||||||
|
margin-right: 6px;
|
||||||
|
margin-top: -2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipText {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipTextStrong {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 15px 0 0;
|
padding: 15px 0 0;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useRef } from "react";
|
|||||||
import { View, Text, Button, Image } from "@tarojs/components";
|
import { View, Text, Button, Image } from "@tarojs/components";
|
||||||
import { Dialog } from "@nutui/nutui-react-taro";
|
import { Dialog } from "@nutui/nutui-react-taro";
|
||||||
import Taro, { useDidShow, useRouter } from "@tarojs/taro";
|
import Taro, { useDidShow, useRouter } from "@tarojs/taro";
|
||||||
import dayjs from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import orderService, {
|
import orderService, {
|
||||||
@@ -21,8 +21,8 @@ import {
|
|||||||
getOrderStatus,
|
getOrderStatus,
|
||||||
generateOrderActions,
|
generateOrderActions,
|
||||||
isPhoneNumber,
|
isPhoneNumber,
|
||||||
|
genGameLength,
|
||||||
} from "@/utils";
|
} from "@/utils";
|
||||||
import { getStorage, setStorage } from "@/store/storage";
|
|
||||||
import { useGlobalStore } from "@/store/global";
|
import { useGlobalStore } from "@/store/global";
|
||||||
import { useOrder } from "@/store/orderStore";
|
import { useOrder } from "@/store/orderStore";
|
||||||
import detailService, { GameData } from "@/services/detailService";
|
import detailService, { GameData } from "@/services/detailService";
|
||||||
@@ -32,7 +32,7 @@ import img from "@/config/images";
|
|||||||
import CustomerIcon from "@/static/order/customer.svg";
|
import CustomerIcon from "@/static/order/customer.svg";
|
||||||
import { handleCustomerService } from "@/services/userService";
|
import { handleCustomerService } from "@/services/userService";
|
||||||
import { requireLoginWithPhone } from "@/utils/helper";
|
import { requireLoginWithPhone } from "@/utils/helper";
|
||||||
import { DECLAIMER } from "./config";
|
import { RegistrationInstructions } from "./config";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
dayjs.locale("zh-cn");
|
dayjs.locale("zh-cn");
|
||||||
@@ -75,6 +75,17 @@ function genGameNotice(order_status, start_time) {
|
|||||||
return gameNoticeMap.get(key) || {};
|
return gameNoticeMap.get(key) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genGameRange(startTime: Dayjs, endTime: Dayjs) {
|
||||||
|
if (!startTime || !endTime) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 如果跨天(自然日)
|
||||||
|
if (!startTime.isSame(endTime, "day")) {
|
||||||
|
return `${startTime.format("HH:mm")} - ${endTime.format("MM月DD日 HH:mm")}`;
|
||||||
|
}
|
||||||
|
return `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
|
||||||
|
}
|
||||||
|
|
||||||
function GameInfo(props) {
|
function GameInfo(props) {
|
||||||
const { detail, currentLocation, orderDetail, init } = props;
|
const { detail, currentLocation, orderDetail, init } = props;
|
||||||
const { order_status, refund_status, amount, refund_amount } = orderDetail;
|
const { order_status, refund_status, amount, refund_amount } = orderDetail;
|
||||||
@@ -111,15 +122,17 @@ function GameInfo(props) {
|
|||||||
|
|
||||||
const startTime = dayjs(start_time);
|
const startTime = dayjs(start_time);
|
||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const game_length = Number(
|
// const game_length = Number(
|
||||||
(endTime.diff(startTime, "minutes") / 60).toFixed(),
|
// (endTime.diff(startTime, "minutes") / 60).toFixed(),
|
||||||
);
|
// );
|
||||||
|
const game_length = genGameLength(startTime, endTime);
|
||||||
|
|
||||||
const startMonth = startTime.format("M");
|
const startMonth = startTime.format("M");
|
||||||
const startDay = startTime.format("D");
|
const startDay = startTime.format("D");
|
||||||
const theDayOfWeek = startTime.format("dddd");
|
const theDayOfWeek = startTime.format("dddd");
|
||||||
const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`;
|
const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`;
|
||||||
const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
|
// const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
|
||||||
|
const gameRange = genGameRange(startTime, endTime);
|
||||||
|
|
||||||
const orderStatus = getOrderStatus(orderDetail);
|
const orderStatus = getOrderStatus(orderDetail);
|
||||||
|
|
||||||
@@ -277,7 +290,7 @@ function GameInfo(props) {
|
|||||||
<View className={styles.gameInfoDateWeatherCalendarDateDate}>
|
<View className={styles.gameInfoDateWeatherCalendarDateDate}>
|
||||||
<View className={styles.date}>{startDate}</View>
|
<View className={styles.date}>{startDate}</View>
|
||||||
<View className={styles.venueTime}>
|
<View className={styles.venueTime}>
|
||||||
{gameRange} ({game_length}小时)
|
{gameRange} ({game_length})
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -550,15 +563,66 @@ function RefundPolicy(props) {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
<Text className={styles.refundTip}>
|
||||||
|
以上时间节点以提交申请时间为准。违约金由组织者(95%)与平台(5%)分配,用于补偿场地损失及处理成本。活动结束48小时后,1个工作日内系统自动结算至组织者账户。未申请退款直接缺席,报名费全额归组织者。
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Disclaimer() {
|
function Disclaimer() {
|
||||||
return (
|
return (
|
||||||
<View className={styles.declaimer}>
|
<View className={styles.disclaimer}>
|
||||||
<Text className={styles.title}>免责声明</Text>
|
<View className={styles.disclaimerTitle}>
|
||||||
<Text className={styles.content}>{DECLAIMER}</Text>
|
<Text>{RegistrationInstructions.title}</Text>
|
||||||
|
</View>
|
||||||
|
<View className={styles.disclaimerDesc}>
|
||||||
|
<Text>{RegistrationInstructions.desc}</Text>
|
||||||
|
</View>
|
||||||
|
{RegistrationInstructions.children.map((section, sectionIndex) => (
|
||||||
|
<View key={sectionIndex} className={styles.disclaimerSection}>
|
||||||
|
<View className={styles.sectionTitle}>
|
||||||
|
<Text>{section.title}</Text>
|
||||||
|
</View>
|
||||||
|
{section.desc && (
|
||||||
|
<View className={styles.sectionDesc}>
|
||||||
|
<Text>{section.desc}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{section.table && (
|
||||||
|
<View className={styles.tableContainer}>
|
||||||
|
{section.table.map((row, rowIndex) => (
|
||||||
|
<View key={rowIndex} className={styles.tableRow}>
|
||||||
|
<View className={styles.tableCell}>
|
||||||
|
<Text>{row.refundApplicationTime}</Text>
|
||||||
|
</View>
|
||||||
|
<View className={styles.tableCell}>
|
||||||
|
<Text>{row.participantRefundableAmount}</Text>
|
||||||
|
</View>
|
||||||
|
<View className={styles.tableCell}>
|
||||||
|
<Text>{row.liquidatedDamages}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{section.tips && (
|
||||||
|
<View className={styles.tipsList}>
|
||||||
|
{section.tips.map((tip, tipIndex) => (
|
||||||
|
<View key={tipIndex} className={styles.tipItem}>
|
||||||
|
<Text
|
||||||
|
className={
|
||||||
|
tip.strong ? styles.tipTextStrong : styles.tipText
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tip.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -627,35 +691,26 @@ const OrderCheck = () => {
|
|||||||
}
|
}
|
||||||
setPaying(true);
|
setPaying(true);
|
||||||
|
|
||||||
let payment_params = {};
|
let payment_params: any = {};
|
||||||
try {
|
try {
|
||||||
payment_params = await getPaymentParams();
|
payment_params = await getPaymentParams();
|
||||||
if (!id) {
|
|
||||||
setStorage("backFlag", "1");
|
|
||||||
Taro.redirectTo({
|
|
||||||
url: `/order_pages/orderDetail/index?id=${payment_params.order_id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await payOrder(payment_params);
|
await payOrder(payment_params);
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: "支付成功",
|
title: "支付成功",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
});
|
});
|
||||||
const backFlag = getStorage("backFlag");
|
// 支付成功后再跳转,避免部分机型(如华为)在页面切换中拉起支付导致卡死
|
||||||
if (backFlag === "1") {
|
if (!id && payment_params?.order_id) {
|
||||||
setStorage("backFlag", "0");
|
Taro.redirectTo({
|
||||||
Taro.navigateBack();
|
url: `/order_pages/orderDetail/index?id=${payment_params.order_id}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Taro.navigateBack({
|
|
||||||
// delta: 1,
|
|
||||||
// });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: error.message,
|
title: error.message,
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setStorage("backFlag", "0");
|
|
||||||
init();
|
init();
|
||||||
setPaying(false);
|
setPaying(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ import DownloadIcon from "@/static/ntrp/ntrp_download.svg";
|
|||||||
import ReTestIcon from "@/static/ntrp/ntrp_re-action.svg";
|
import ReTestIcon from "@/static/ntrp/ntrp_re-action.svg";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
|
/** 微信小程序码 scene 最长 32 字符 */
|
||||||
|
const WX_SCENE_MAX_LEN = 32;
|
||||||
|
|
||||||
|
/** 分享图/太阳码:r={record_id},落地后在 NtrpEvaluate 归一化为 stage=result&id=&from_share=1 */
|
||||||
|
function buildNtrpShareScene(recordId: string | number): string {
|
||||||
|
const scene = `r=${recordId}`;
|
||||||
|
if (scene.length > WX_SCENE_MAX_LEN) {
|
||||||
|
console.warn("[ntrp-evaluate] share scene exceeds WeChat limit:", scene);
|
||||||
|
}
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
const sourceTypeToTextMap = new Map([
|
const sourceTypeToTextMap = new Map([
|
||||||
[EvaluateScene.detail, "继续加入球局"],
|
[EvaluateScene.detail, "继续加入球局"],
|
||||||
[EvaluateScene.publish, "继续发布球局"],
|
[EvaluateScene.publish, "继续发布球局"],
|
||||||
@@ -485,7 +497,8 @@ function Test() {
|
|||||||
|
|
||||||
function Result() {
|
function Result() {
|
||||||
const { params } = useRouter();
|
const { params } = useRouter();
|
||||||
const { id } = params;
|
const { id, from_share } = params;
|
||||||
|
const fromShare = from_share === "1" || from_share === "true";
|
||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
const { fetchUserInfo, updateUserInfo } = useUserActions();
|
const { fetchUserInfo, updateUserInfo } = useUserActions();
|
||||||
const { type, next, clear } = useEvaluate();
|
const { type, next, clear } = useEvaluate();
|
||||||
@@ -514,13 +527,14 @@ function Result() {
|
|||||||
init();
|
init();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
// 获取二维码 - 调用接口生成分享二维码
|
// 获取二维码 - 太阳码携带当次 record_id,扫码进入分享结果页
|
||||||
async function fetchQRCode() {
|
async function fetchQRCode() {
|
||||||
try {
|
try {
|
||||||
// 调用接口生成二维码,分享当前页面
|
const recordId = id != null && id !== "" ? String(id) : "";
|
||||||
|
if (!recordId) return;
|
||||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||||
page: "other_pages/ntrp-evaluate/index",
|
page: "other_pages/ntrp-evaluate/index",
|
||||||
scene: `stage=${StageType.INTRO}`,
|
scene: buildNtrpShareScene(recordId),
|
||||||
});
|
});
|
||||||
setQrCodeUrl(qrCodeUrlRes.data.ossPath);
|
setQrCodeUrl(qrCodeUrlRes.data.ossPath);
|
||||||
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||||||
@@ -536,29 +550,57 @@ function Result() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getResultById() {
|
async function getResultById() {
|
||||||
const res = await evaluateService.getTestResult({ record_id: Number(id) });
|
const recordId = Number(id);
|
||||||
if (res.code === 0) {
|
if (!id || Number.isNaN(recordId) || recordId <= 0) {
|
||||||
setResult(res.data);
|
Taro.showToast({ title: "链接无效", icon: "none" });
|
||||||
|
Taro.redirectTo({
|
||||||
const sortOrder = res.data.sort || [];
|
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
||||||
const abilities = res.data.radar_data.abilities;
|
});
|
||||||
const sortedKeys = sortOrder.filter((k) => k in abilities);
|
return;
|
||||||
const remainingKeys = Object.keys(abilities).filter(
|
}
|
||||||
(k) => !sortOrder.includes(k),
|
try {
|
||||||
|
const res = await evaluateService.getTestResult(
|
||||||
|
{
|
||||||
|
record_id: recordId,
|
||||||
|
...(fromShare ? { from_share: true } : {}),
|
||||||
|
},
|
||||||
|
fromShare ? { showToast: false } : undefined,
|
||||||
);
|
);
|
||||||
const allKeys = [...sortedKeys, ...remainingKeys];
|
if (res.code === 0) {
|
||||||
let radarData: [string, number][] = allKeys.map((key) => [
|
setResult(res.data);
|
||||||
key,
|
|
||||||
Math.min(
|
const sortOrder = res.data.sort || [];
|
||||||
100,
|
const abilities = res.data.radar_data.abilities;
|
||||||
Math.floor(
|
const sortedKeys = sortOrder.filter((k) => k in abilities);
|
||||||
(abilities[key].current_score / abilities[key].max_score) * 100,
|
const remainingKeys = Object.keys(abilities).filter(
|
||||||
|
(k) => !sortOrder.includes(k),
|
||||||
|
);
|
||||||
|
const allKeys = [...sortedKeys, ...remainingKeys];
|
||||||
|
const nextRadarData: [string, number][] = allKeys.map((key) => [
|
||||||
|
key,
|
||||||
|
Math.min(
|
||||||
|
100,
|
||||||
|
Math.floor(
|
||||||
|
(abilities[key].current_score / abilities[key].max_score) * 100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
]);
|
||||||
]);
|
setRadarData(nextRadarData);
|
||||||
// 直接使用接口 sort 顺序,不经过 adjustRadarLabels 重新排序
|
if (!fromShare) {
|
||||||
setRadarData(radarData);
|
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
||||||
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
}
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
if (fromShare) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: e?.message || "分享已失效或无权查看",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
Taro.redirectTo({
|
||||||
|
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 本人结果页失败时 httpService 已 toast,此处不再重复
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,10 +657,11 @@ function Result() {
|
|||||||
// 确保二维码已获取,如果没有则重新获取
|
// 确保二维码已获取,如果没有则重新获取
|
||||||
let finalQrCodeUrl = qrCodeUrl;
|
let finalQrCodeUrl = qrCodeUrl;
|
||||||
if (!finalQrCodeUrl) {
|
if (!finalQrCodeUrl) {
|
||||||
// 直接调用接口获取二维码
|
const recordId = id != null && id !== "" ? String(id) : "";
|
||||||
|
if (!recordId) throw new Error("缺少测评记录");
|
||||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||||
page: "other_pages/ntrp-evaluate/index",
|
page: "other_pages/ntrp-evaluate/index",
|
||||||
scene: `stage=${StageType.INTRO}`,
|
scene: buildNtrpShareScene(recordId),
|
||||||
});
|
});
|
||||||
finalQrCodeUrl = qrCodeUrlRes.data.ossPath;
|
finalQrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||||
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||||||
@@ -628,16 +671,20 @@ function Result() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 RadarV2 的 generateFullImage 方法生成完整图片
|
const posterNickname = fromShare
|
||||||
const userNickname = (userInfo as any)?.nickname;
|
? result?.sharer_nickname || "好友"
|
||||||
const titleText = userNickname
|
: (userInfo as any)?.nickname;
|
||||||
? `${userNickname}的 NTRP 测试结果为`
|
const titleText = posterNickname
|
||||||
|
? `${posterNickname}的 NTRP 测试结果为`
|
||||||
: "你的 NTRP 测试结果为";
|
: "你的 NTRP 测试结果为";
|
||||||
|
const posterAvatar = fromShare
|
||||||
|
? result?.sharer_avatar_url || (userInfo as any)?.avatar_url
|
||||||
|
: (userInfo as any)?.avatar_url;
|
||||||
const imageUrl = await radarV2Ref.current?.generateFullImage({
|
const imageUrl = await radarV2Ref.current?.generateFullImage({
|
||||||
title: titleText,
|
title: titleText,
|
||||||
ntrpLevel: result?.ntrp_level,
|
ntrpLevel: result?.ntrp_level,
|
||||||
levelDescription: result?.level_description,
|
levelDescription: result?.level_description,
|
||||||
avatarUrl: (userInfo as any)?.avatar_url,
|
avatarUrl: posterAvatar,
|
||||||
qrCodeUrl: finalQrCodeUrl,
|
qrCodeUrl: finalQrCodeUrl,
|
||||||
bottomText: "长按识别二维码,快来加入,有你就有场!",
|
bottomText: "长按识别二维码,快来加入,有你就有场!",
|
||||||
width: 750, // 设计稿宽度
|
width: 750, // 设计稿宽度
|
||||||
@@ -651,8 +698,7 @@ function Result() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSaveImage() {
|
async function handleSaveImage() {
|
||||||
console.log(userInfo);
|
if (!fromShare && !userInfo?.phone) {
|
||||||
if (!userInfo?.phone) {
|
|
||||||
handleAuth();
|
handleAuth();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -691,12 +737,21 @@ function Result() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useShareAppMessage(async (res) => {
|
useShareAppMessage(async () => {
|
||||||
console.log("res", result);
|
const rid = result?.record_id ?? Number(id);
|
||||||
|
const sharePath =
|
||||||
|
rid && !Number.isNaN(Number(rid))
|
||||||
|
? `/other_pages/ntrp-evaluate/index?stage=${StageType.RESULT}&id=${rid}&from_share=1`
|
||||||
|
: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`;
|
||||||
|
const shareNickname = fromShare
|
||||||
|
? result?.sharer_nickname || "好友"
|
||||||
|
: (userInfo as any)?.nickname || "好友";
|
||||||
return {
|
return {
|
||||||
title: "来测一测你的NTRP等级吧",
|
title: result?.ntrp_level
|
||||||
|
? `来看看 ${shareNickname} 的测评结果`
|
||||||
|
: "来测一测你的NTRP等级吧",
|
||||||
imageUrl: result?.level_img || undefined,
|
imageUrl: result?.level_img || undefined,
|
||||||
path: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
path: sharePath,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -714,6 +769,18 @@ function Result() {
|
|||||||
|
|
||||||
function handleGo() {}
|
function handleGo() {}
|
||||||
|
|
||||||
|
const cardTitleText = fromShare
|
||||||
|
? result?.sharer_nickname
|
||||||
|
? `${result.sharer_nickname}的 NTRP 测试结果为`
|
||||||
|
: "好友分享的 NTRP 测试结果为"
|
||||||
|
: (userInfo as any)?.nickname
|
||||||
|
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
|
||||||
|
: "你的 NTRP 测试结果为";
|
||||||
|
|
||||||
|
const cardAvatarUrl = fromShare
|
||||||
|
? result?.sharer_avatar_url || ""
|
||||||
|
: userInfo?.avatar_url || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={styles.resultContainer}>
|
<View className={styles.resultContainer}>
|
||||||
<CommonGuideBar />
|
<CommonGuideBar />
|
||||||
@@ -727,7 +794,7 @@ function Result() {
|
|||||||
<View className={styles.avatar}>
|
<View className={styles.avatar}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.avatarUrl}
|
className={styles.avatarUrl}
|
||||||
src={userInfo?.avatar_url || ""}
|
src={cardAvatarUrl}
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -742,11 +809,7 @@ function Result() {
|
|||||||
</View>
|
</View>
|
||||||
<View className={styles.desc}>
|
<View className={styles.desc}>
|
||||||
<View className={styles.tip}>
|
<View className={styles.tip}>
|
||||||
<Text>
|
<Text>{cardTitleText}</Text>
|
||||||
{(userInfo as any)?.nickname
|
|
||||||
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
|
|
||||||
: "你的 NTRP 测试结果为"}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.levelWrap}>
|
<View className={styles.levelWrap}>
|
||||||
<Text>NTRP</Text>
|
<Text>NTRP</Text>
|
||||||
@@ -767,7 +830,7 @@ function Result() {
|
|||||||
<Text>重新测试</Text>
|
<Text>重新测试</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{userInfo?.phone ? (
|
{userInfo?.phone && !fromShare ? (
|
||||||
<View className={styles.updateTip}>
|
<View className={styles.updateTip}>
|
||||||
<Text>
|
<Text>
|
||||||
你的 NTRP 水平已更新为{" "}
|
你的 NTRP 水平已更新为{" "}
|
||||||
@@ -775,11 +838,17 @@ function Result() {
|
|||||||
</Text>
|
</Text>
|
||||||
<Text className={styles.grayTip}>(可在个人信息中修改)</Text>
|
<Text className={styles.grayTip}>(可在个人信息中修改)</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : !userInfo?.phone ? (
|
||||||
<View className={styles.updateTip}>
|
<View className={styles.updateTip}>
|
||||||
<Text>登录「有场」小程序,查看匹配你的球局</Text>
|
<Text>登录「有场」小程序,查看匹配你的球局</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
) : fromShare ? (
|
||||||
|
<View className={styles.updateTip}>
|
||||||
|
<Text className={styles.grayTip}>
|
||||||
|
以上为好友分享结果,去测一测你的 NTRP 吧
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<View className={styles.actions}>
|
<View className={styles.actions}>
|
||||||
<View className={styles.viewGame} onClick={handleGoon}>
|
<View className={styles.viewGame} onClick={handleGoon}>
|
||||||
<Button className={styles.viewGameBtn}>
|
<Button className={styles.viewGameBtn}>
|
||||||
@@ -821,14 +890,10 @@ function Result() {
|
|||||||
<RadarChartV2
|
<RadarChartV2
|
||||||
ref={radarV2Ref}
|
ref={radarV2Ref}
|
||||||
data={radarData}
|
data={radarData}
|
||||||
title={
|
title={cardTitleText}
|
||||||
(userInfo as any)?.nickname
|
|
||||||
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
|
|
||||||
: "你的 NTRP 测试结果为"
|
|
||||||
}
|
|
||||||
ntrpLevel={result?.ntrp_level}
|
ntrpLevel={result?.ntrp_level}
|
||||||
levelDescription={result?.level_description}
|
levelDescription={result?.level_description}
|
||||||
avatarUrl={(userInfo as any)?.avatar_url}
|
avatarUrl={cardAvatarUrl}
|
||||||
qrCodeUrl={qrCodeUrl}
|
qrCodeUrl={qrCodeUrl}
|
||||||
bottomText="长按识别二维码,快来加入,有你就有场!"
|
bottomText="长按识别二维码,快来加入,有你就有场!"
|
||||||
/>
|
/>
|
||||||
@@ -844,9 +909,21 @@ const ComponentsMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function NtrpEvaluate() {
|
function NtrpEvaluate() {
|
||||||
// const { updateUserInfo } = useUserActions();
|
|
||||||
const { params } = useRouter();
|
const { params } = useRouter();
|
||||||
// const { redirect } = params;
|
|
||||||
|
// 太阳码 scene 仅支持 32 字符,用 r=id 落地后归一化为结果页 + from_share
|
||||||
|
useEffect(() => {
|
||||||
|
const r = params.r;
|
||||||
|
if (r && !params.stage) {
|
||||||
|
Taro.redirectTo({
|
||||||
|
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.RESULT}&id=${encodeURIComponent(String(r))}&from_share=1`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [params.r, params.stage]);
|
||||||
|
|
||||||
|
if (params.r && !params.stage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const stage = params.stage as StageType;
|
const stage = params.stage as StageType;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import img from '@/config/images';
|
|||||||
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
|
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
|
||||||
import SelectStadium from '../SelectStadium/SelectStadium'
|
import SelectStadium from '../SelectStadium/SelectStadium'
|
||||||
import { Stadium } from '../SelectStadium/StadiumDetail'
|
import { Stadium } from '../SelectStadium/StadiumDetail'
|
||||||
|
import { normalize_address } from '@/utils/locationUtils'
|
||||||
import './FormBasicInfo.scss'
|
import './FormBasicInfo.scss'
|
||||||
|
|
||||||
type PlayGame = {
|
type PlayGame = {
|
||||||
@@ -54,7 +55,7 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
|
|||||||
onChange({...value,
|
onChange({...value,
|
||||||
venue_id,
|
venue_id,
|
||||||
location_name: name,
|
location_name: name,
|
||||||
location: address,
|
location: normalize_address(address || ''),
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
court_type,
|
court_type,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Taro from '@tarojs/taro'
|
|||||||
import { Loading } from '@nutui/nutui-react-taro'
|
import { Loading } from '@nutui/nutui-react-taro'
|
||||||
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
|
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
|
||||||
import { CommonPopup, CustomPopup } from '../../../../components'
|
import { CommonPopup, CustomPopup } from '../../../../components'
|
||||||
import { getLocation } from '@/utils/locationUtils'
|
import { getLocation, normalize_address } from '@/utils/locationUtils'
|
||||||
import PublishService from '@/services/publishService'
|
import PublishService from '@/services/publishService'
|
||||||
import images from '@/config/images'
|
import images from '@/config/images'
|
||||||
import './SelectStadium.scss'
|
import './SelectStadium.scss'
|
||||||
@@ -100,7 +100,7 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
|||||||
success: (res) => {
|
success: (res) => {
|
||||||
setSelectedStadium({
|
setSelectedStadium({
|
||||||
name: res.name,
|
name: res.name,
|
||||||
address: res.address,
|
address: normalize_address(res.address || ''),
|
||||||
longitude: res.longitude,
|
longitude: res.longitude,
|
||||||
latitude: res.latitude
|
latitude: res.latitude
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TextareaTag from '@/components/TextareaTag'
|
|||||||
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
||||||
import { useKeyboardHeight } from '@/store/keyboardStore'
|
import { useKeyboardHeight } from '@/store/keyboardStore'
|
||||||
import { useDictionaryActions } from '@/store/dictionaryStore'
|
import { useDictionaryActions } from '@/store/dictionaryStore'
|
||||||
|
import { normalize_address } from '@/utils/locationUtils'
|
||||||
|
|
||||||
import './StadiumDetail.scss'
|
import './StadiumDetail.scss'
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
name: res.name,
|
name: res.name,
|
||||||
address: res.address,
|
address: normalize_address(res.address || ''),
|
||||||
latitude: res.latitude,
|
latitude: res.latitude,
|
||||||
longitude: res.longitude,
|
longitude: res.longitude,
|
||||||
istance: null
|
istance: null
|
||||||
|
|||||||
@@ -381,6 +381,7 @@ const PublishBall: React.FC = () => {
|
|||||||
image_list,
|
image_list,
|
||||||
wechat,
|
wechat,
|
||||||
id,
|
id,
|
||||||
|
title,
|
||||||
...rest
|
...rest
|
||||||
} = formData[0];
|
} = formData[0];
|
||||||
const { min, max, organizer_joined } = players;
|
const { min, max, organizer_joined } = players;
|
||||||
@@ -389,6 +390,7 @@ const PublishBall: React.FC = () => {
|
|||||||
...activityInfo,
|
...activityInfo,
|
||||||
...descriptionInfo,
|
...descriptionInfo,
|
||||||
...timeRange,
|
...timeRange,
|
||||||
|
title: title?.replace(/\n/g, ''),
|
||||||
max_players: max,
|
max_players: max,
|
||||||
min_players: min,
|
min_players: min,
|
||||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||||
@@ -453,6 +455,7 @@ const PublishBall: React.FC = () => {
|
|||||||
skill_level,
|
skill_level,
|
||||||
is_substitute_supported,
|
is_substitute_supported,
|
||||||
id,
|
id,
|
||||||
|
title,
|
||||||
...rest
|
...rest
|
||||||
} = item;
|
} = item;
|
||||||
const { min, max, organizer_joined } = players;
|
const { min, max, organizer_joined } = players;
|
||||||
@@ -461,6 +464,7 @@ const PublishBall: React.FC = () => {
|
|||||||
...activityInfo,
|
...activityInfo,
|
||||||
...descriptionInfo,
|
...descriptionInfo,
|
||||||
...timeRange,
|
...timeRange,
|
||||||
|
title: title?.replace(/\n/g, ' '),
|
||||||
max_players: max,
|
max_players: max,
|
||||||
min_players: min,
|
min_players: min,
|
||||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import httpService from "./httpService";
|
import httpService from "./httpService";
|
||||||
import type { ApiResponse } from "./httpService";
|
import type { ApiResponse, RequestConfig } from "./httpService";
|
||||||
|
|
||||||
export enum StageType {
|
export enum StageType {
|
||||||
INTRO = "intro",
|
INTRO = "intro",
|
||||||
@@ -59,6 +59,9 @@ export interface TestResultData {
|
|||||||
radar_data: RadarData;
|
radar_data: RadarData;
|
||||||
answers: Answer[];
|
answers: Answer[];
|
||||||
sort?: string[]; // 雷达图能力项排序,如 ["正手球质", "正手控制", ...]
|
sort?: string[]; // 雷达图能力项排序,如 ["正手球质", "正手控制", ...]
|
||||||
|
/** 分享查看时后端可返回测评归属用户,用于展示头像昵称 */
|
||||||
|
sharer_nickname?: string;
|
||||||
|
sharer_avatar_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单条测试记录
|
// 单条测试记录
|
||||||
@@ -118,11 +121,15 @@ class EvaluateService {
|
|||||||
return httpService.post("/ntrp/history", {}, { showLoading: true });
|
return httpService.post("/ntrp/history", {}, { showLoading: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取测试详情
|
// 获取测试详情(from_share 为 true 时表示扫码/分享查看他人当次结果,需后端 /ntrp/detail 按 record_id 放行只读)
|
||||||
async getTestResult(req: {
|
async getTestResult(
|
||||||
record_id: number;
|
req: { record_id: number; from_share?: boolean },
|
||||||
}): Promise<ApiResponse<TestResultData>> {
|
httpConfig?: Partial<RequestConfig>,
|
||||||
return httpService.post("/ntrp/detail", req, { showLoading: true });
|
): Promise<ApiResponse<TestResultData>> {
|
||||||
|
return httpService.post("/ntrp/detail", req, {
|
||||||
|
showLoading: true,
|
||||||
|
...httpConfig,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取最后一次(最新)测试结果
|
// 获取最后一次(最新)测试结果
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export class UserService {
|
|||||||
|
|
||||||
// 处理人数统计 - 兼容不同的字段名
|
// 处理人数统计 - 兼容不同的字段名
|
||||||
const registered_count =
|
const registered_count =
|
||||||
game.current_players || game.participant_count || 0;
|
game.participant_count ?? game.current_players ?? 0;
|
||||||
const max_count = game.max_players || game.max_participants || 0;
|
const max_count = game.max_players || game.max_participants || 0;
|
||||||
|
|
||||||
// 转换为 ListCard 期望的格式
|
// 转换为 ListCard 期望的格式
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
<svg width="24" height="32" viewBox="0 0 24 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<g clip-path="url(#clip0_7504_18610)">
|
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 74.87 89.85">
|
||||||
<path d="M12.3076 0.0971837C12.7381 0.438102 12.2325 2.22445 12.328 2.76282C13.7134 2.51292 15.1145 2.01928 16.5062 1.81874C16.8928 1.76321 17.9933 1.57501 17.89 2.28307C17.8274 2.71346 16.6377 4.52912 16.73 4.62167C17.9667 4.78673 19.2128 4.97647 20.4214 5.28962C20.8346 5.39607 21.9226 5.57501 21.8209 6.15503C21.7223 6.71192 20.0394 8.18974 20.0848 8.37331C21.1211 9.25414 22.4017 9.9838 23.402 10.8909C23.712 11.1716 24.119 11.457 23.9671 11.926C23.8028 12.4366 22.0009 12.6541 21.5469 12.9965C21.525 13.1215 22.552 14.7351 22.7069 15.0204C22.8995 15.3752 23.8028 17.0613 23.7871 17.3236C23.726 18.2754 22.051 17.5827 21.4827 17.6383C21.6596 18.3648 21.8131 19.1408 21.8929 19.8843C21.9774 20.6679 22.2514 21.6753 21.0632 21.4932C21.237 29.4763 12.3624 34.5546 5.2928 30.6641C-0.183159 27.6514 -1.69069 20.6001 2.09614 15.6467C2.21199 15.494 2.76459 14.9402 2.78964 14.8801C2.81782 14.8122 2.73642 14.2831 2.75364 14.0902C2.80999 13.4624 3.15439 12.8824 3.58802 12.4366C2.80686 11.693 1.97874 10.8461 1.52945 9.85268C1.36977 9.50096 1.30559 9.0027 1.16 8.70189C1.04103 8.45661 0.72324 8.31623 0.593307 8.0216C0.190985 7.11762 0.835953 6.08561 1.87228 6.20902C2.38262 6.26919 2.4609 6.6317 3.13091 6.54069C4.43807 6.36637 5.06425 4.00154 5.53545 2.98804C5.73583 2.5592 6.59839 0.737369 6.94749 0.59082C7.976 0.161974 7.82102 2.05939 8.23899 2.54994C9.08747 1.94986 9.8702 1.2526 10.7281 0.664866C11.0991 0.410335 11.8724 -0.251447 12.3076 0.0925559V0.0971837ZM11.1993 6.0563C10.9629 6.20285 10.2944 5.55804 10.022 5.41149C8.99824 4.86232 7.73648 4.72348 6.62814 5.09988C5.22236 5.57809 5.13626 6.89394 3.68039 7.42923C3.52854 7.48477 3.02133 7.55418 2.9775 7.59738C2.94462 7.62977 2.87418 7.99846 2.75364 8.16814C2.6331 8.33475 2.4061 8.51832 2.19477 8.55071C2.40297 9.81874 3.62873 11.7146 5.15661 11.0189C5.63407 10.8014 5.73583 10.263 5.89707 10.1643C6.15694 10.0069 6.41367 10.1658 6.45437 10.4466C6.57178 11.258 5.30689 11.9676 4.57113 11.9213L5.26776 12.3702C6.99289 13.2865 8.93719 13.6429 10.8846 13.3575C10.9989 13.322 11.2165 12.5245 11.3605 12.3116C11.5515 12.0278 11.9178 11.8072 12.1182 11.5418C12.5033 11.0297 12.6598 9.76166 12.6066 9.13228C12.5847 8.87466 12.3452 8.2931 12.4297 8.13266C12.9792 7.84265 13.5897 7.80409 13.8793 7.14693C14.5384 5.64905 12.6035 4.40879 11.4904 5.55033C11.3777 5.66602 11.2259 6.03934 11.2008 6.05476L11.1993 6.0563ZM12.8665 12.2808C12.2513 12.418 11.6439 13.683 12.1511 14.1273C12.7882 14.6826 14.2582 13.5488 14.6981 13.049C14.4006 12.5183 13.4473 12.1512 12.8665 12.2808ZM6.45594 13.7802C6.16163 13.7324 5.8642 13.6645 5.58085 13.575C4.81847 13.3313 4.25647 12.7312 3.85572 13.7324C3.66786 14.1998 3.64751 14.823 4.27213 14.9387C4.7809 15.0328 6.34323 14.4142 6.45594 13.7817V13.7802ZM10.9973 13.8511C9.58215 14.1921 8.2343 14.1859 6.80817 13.919C6.76746 14.8091 5.6012 15.4477 4.79656 15.5526C4.52573 15.5881 4.07645 15.5156 3.87294 15.6066C3.74144 15.6653 3.02603 16.4165 2.89609 16.5708C0.767073 19.1192 0.635574 22.5885 2.06484 25.5118C2.36853 26.1319 2.43428 26.3756 3.20918 26.4512C5.43839 26.6672 6.16633 25.3606 7.9713 24.6016C11.4137 23.1562 15.7767 24.2237 17.6051 27.5526C20.7376 24.0617 20.4417 18.7906 17.2576 15.4323C16.9257 15.0837 15.7328 14.118 15.3117 13.9699C15.0675 13.8835 14.8123 14.297 14.6292 14.4296C13.3659 15.3521 11.3589 15.9105 10.9989 13.8527L10.9973 13.8511ZM3.59115 27.6915C3.55985 27.8103 3.64125 27.8288 3.69447 27.8982C4.0968 28.4103 4.98284 29.0305 5.55267 29.3729C8.98102 31.4354 13.5036 31.1145 16.5719 28.5553C15.415 25.5164 11.647 24.6232 8.74463 25.6429C6.98819 26.2599 5.62468 28.0972 3.59115 27.6915Z" fill="black"/>
|
<defs>
|
||||||
<path d="M7.69734 6.77362C8.84169 6.70883 9.28002 8.38257 8.18264 8.8361C6.57804 9.49788 6.06457 6.86463 7.69734 6.77362Z" fill="black"/>
|
<style>
|
||||||
</g>
|
.cls-1 {
|
||||||
<defs>
|
fill: #e6ff54;
|
||||||
<clipPath id="clip0_7504_18610">
|
}
|
||||||
<rect width="24" height="32" fill="white"/>
|
</style>
|
||||||
</clipPath>
|
</defs>
|
||||||
</defs>
|
<g id="_图层_1-2" data-name=" 图层 1">
|
||||||
</svg>
|
<path class="cls-1" d="M39.79,82.22h0c-3.1,1.7-6.7,2.6-10.3,2.6-9.9,0-18.3-6.7-21-15.7,1.7,1.4,3.6,2.3,5.6,2.7,4.7,1,8.3,0,11.8-1,3.1-.8,5.9-1.6,9.7-1.2,3.9.5,6.6,2.6,7.3,5.7.6,2.7-.5,5.4-3,6.9h-.1Z"/>
|
||||||
|
<path class="cls-1" d="M51.29,62.92c0,.9,0,1.9-.2,2.8-.5,3.7-1.8,7.2-4,10.2,0-.5,0-1-.2-1.5-1.1-4.9-5.3-8.3-10.9-9-4.5-.6-8.1.4-11.3,1.3-3.3,1-6.2,1.7-9.9,1-3.9-.8-7.6-4.7-6.6-10.4h0c0-.6.3-1.1.5-1.7,0-.4.3-.8.5-1.2,1.1.2,2.2.2,3.3,0h.4c2.6-.9,4.4-2.8,4.8-5.2v-.2c4.8,1.4,10.4,1.3,15.4,0h0c.2-.2.4-.2.6-.3,0,1,.5,2,1.1,2.9,1.1,1.6,2.9,2.6,5,2.8,1.1,0,2.2,0,3.3-.4h0c1-.2,2-.7,3-1.3.6-.4,1.1-.8,1.6-1.2,2.3,3.5,3.5,7.6,3.5,11.9v-.3l.1-.2Z"/>
|
||||||
|
<g>
|
||||||
|
<path d="M74.59,37.52c-2.1-6.1-6.4-11.7-12.2-15.9h0c1.7-2,2.6-3,4.5-4.6.5-.4.7-1,.6-1.6,0-.6-.4-1.1-1-1.4-6.5-3.2-12.1-4.7-18.9-5,.9-2.4,1.8-4.1,3-6.4.3-.6.3-1.3,0-1.9-.4-.6-1.1-.8-1.8-.7-7.6,1.7-13.8,4-20.9,8.1-1.9-2.4-4.1-4.3-6.8-6-.5-.3-1.2-.3-1.8,0s-.9.9-.9,1.5c0,5.6-1,9.9-3.2,14.4-1.6,3.3-3.3,5.7-4.4,7.4-1.3,1.9-2.1,2.9-3,3.4-1.35-1.86-4.14-2.86-6.42-.58-1.03,1.03-1.51,2.46-1.33,3.91.21,1.67,1.05,2.27,2.25,2.87.7,2.3,1.7,4.5,2.9,6.3-1.4,1.1-2.3,2.7-2.7,4.5-.6,2.7,0,5.3,1.9,7.2-1.2,3.1-1.8,6.4-1.8,9.8,0,14.9,13.39,27.65,27,27,6.31-.3,9.55-.58,15.79-5.21,1.57-1.16,3.02-2.5,4.26-4.01,3.31-4.03,5.89-8.65,6.65-13.38,1.2.4,2.4.9,3.3,1.3.4.2.8.2,1.3,0h.2c.5-.2.8-.6,1-1.1.9-2.9,1.4-7.5,1.6-10.6,1.5,0,3.1.3,5.2.7.5,0,1.1,0,1.5-.4s.6-.9.6-1.4c-.2-5.6-1-10-2.5-14.3,1.8-.8,3.3-1.4,5.1-1.8.5,0,.9-.4,1.1-.9.2-.4.3-.9,0-1.4v.2h-.1ZM44.09,41.82c.7,0,1.2.3,1.7.8l.17.17c.26.26.52,1.15.72,1.96.29,1.21-.08,2.48-.97,3.36,0,0-.01.01-.02.02-.4.4-.8.7-1.3,1-1.4.9-2.8,1.3-4.1,1.2-1,0-1.8-.5-2.3-1.2-.5-.8-.7-1.6-.5-2.4,0-.4.2-.8.4-1.2.2-.3.4-.6.6-.9h0c.2-.3.5-.6.8-.8s.6-.4.9-.6c.8-.5,1.6-.9,2.3-1.1.48-.09.65-.19,1.21-.27l.39-.03ZM7.09,34.12h.4c3.7-.5,5.6-3.2,7.3-5.8,1.3-1.8,2.6-3.8,4.8-5.3,3.7-2.5,8.6-2.6,12.6,0,.3.2.6.2,1,.2h.4c.5,0,.9-.5,1.1-.9.6-1.3,1.7-2.2,3-2.4,1.1-.2,2.1,0,2.8.7.8.7,1.2,1.8,1,2.9-.2,1.2-1.2,2.1-2.6,2.7-.6.2-.9.6-1.1,1.2-.2.6,0,1.2.3,1.6,2.1,2.7,2.9,6.5,2.1,9.5,0,.3-.2.6-.3.9-.6.3-1.1.6-1.7,1-1.6,1-2.8,2.2-3.5,3.6,0,0-.2.3-.2.5l-1.1.3c-1.1.4-2.2.7-3.4.9-2.7.5-5.5.6-8.2.2-1.2-.2-2.3-.4-3.4-.8h0c-.4,0-.8-.2-1.2-.4-.3,0-.5-.2-.8-.3h-.3c-.2,0-.4-.2-.7-.3-.2,0-.4-.2-.6-.3.2,0,.4-.2.6-.4,0,0,.2,0,.3-.2,1.1-.9,2.1-2.1,2.7-3.7.2-.5.2-1,0-1.5s-.6-.9-1.1-1c-1-.4-2.2,0-2.6,1.1-.6,1.4-1.5,2.3-2.6,2.4-.5,0-1.1,0-1.7-.3h0c-1.3-1.6-2.3-3.5-3-5.6v-.2h-.2l-.1-.3ZM7.79,49.72c-.2-.2-.3-.5-.5-.9h0c0-.4-.2-.8,0-1.4h0v-.5c.3-1.4,1.2-2,1.8-2.3h.7c.2,0,.3.3.5.4.2.2.5.3.7.5.4.2.7.5,1.1.7.5.3,1,.6,1.5.8,0,0,.2,0,.4.2h0v1.1c-.2,1.1-1.1,2-2.5,2.3h-.5c-.8,0-1.6,0-2.3-.3-.3-.2-.6-.4-.8-.6s0,0-.2-.2h.2l-.1.2ZM39.79,82.22h0c-3.1,1.7-6.7,2.6-10.3,2.6-9.9,0-18.3-6.7-21-15.7,1.7,1.4,3.6,2.3,5.6,2.7,4.7,1,8.3,0,11.8-1,3.1-.8,5.9-1.6,9.7-1.2,3.9.5,6.6,2.6,7.3,5.7.6,2.7-.5,5.4-3,6.9h-.1ZM47.09,75.92c0-.5,0-1-.2-1.5-1.1-4.9-5.3-8.3-10.9-9-4.5-.6-8.1.4-11.3,1.3-3.3,1-6.2,1.7-9.9,1-3.9-.8-7.6-4.7-6.6-10.4h0c0-.6.3-1.1.5-1.7,0-.4.3-.8.5-1.2,1.1.2,2.2.2,3.3,0h.4c2.6-.9,4.4-2.8,4.8-5.2v-.2c4.8,1.4,10.2,1.4,15.1,0,0,0,.6-.3.8-.4,0,1,.5,2,1.1,2.9,1.1,1.6,2.9,2.6,5,2.8,1.1,0,3.4-.3,3.4-.3,1-.3,2-.8,3-1.4.6-.4,1.1-.8,1.6-1.2,2.3,3.5,3.52,7.6,3.5,11.9-.01,2.59,0,1.9-.2,2.8,0,0-1.7,6.8-3.9,9.8Z"/>
|
||||||
|
<path d="M24.69,34.92c-1.6.2-3.1-.9-3.3-2.5-.3-1.6.8-3.1,2.4-3.3h0c1.6-.2,3.1.9,3.3,2.5.3,1.6-.8,3.1-2.4,3.3Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -319,8 +319,9 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
|
|
||||||
|
|
||||||
console.log("start !!!!");
|
console.log("start !!!!");
|
||||||
// const dpr = Taro.getWindowInfo().pixelRatio;
|
const systemDpr =
|
||||||
const dpr = 1;
|
Taro.getWindowInfo?.().pixelRatio || Taro.getSystemInfoSync().pixelRatio || 1;
|
||||||
|
const dpr = Math.min(Math.max(systemDpr, 1), 2);
|
||||||
// console.log(dpr, 'dpr')
|
// console.log(dpr, 'dpr')
|
||||||
const width = 600;
|
const width = 600;
|
||||||
const height = 1000;
|
const height = 1000;
|
||||||
@@ -487,7 +488,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||||
canvas,
|
canvas,
|
||||||
fileType: 'png',
|
fileType: 'png',
|
||||||
quality: 0.7,
|
quality: 1,
|
||||||
});
|
});
|
||||||
console.log('tempFilePath', tempFilePath)
|
console.log('tempFilePath', tempFilePath)
|
||||||
return tempFilePath;
|
return tempFilePath;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import { check_login_status, get_user_info } from "@/services/loginService";
|
import { check_login_status, get_user_info } from "@/services/loginService";
|
||||||
import { useUser } from "@/store/userStore";
|
import { useUser } from "@/store/userStore";
|
||||||
|
import { Dayjs } from "dayjs";
|
||||||
|
|
||||||
// 普通函数,不调用 useLoad
|
// 普通函数,不调用 useLoad
|
||||||
export const sceneRedirectLogic = (options, defaultPage: string) => {
|
export const sceneRedirectLogic = (options, defaultPage: string) => {
|
||||||
@@ -135,3 +136,40 @@ export function genNTRPRequirementText(min, max) {
|
|||||||
export function isPhoneNumber(str) {
|
export function isPhoneNumber(str) {
|
||||||
return /^1[3-9]\d{9}$/.test(str);
|
return /^1[3-9]\d{9}$/.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function genGameLength(startTime: Dayjs, endTime: Dayjs) {
|
||||||
|
if (!startTime || !endTime) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const totalMinutes = endTime.diff(startTime, "minute");
|
||||||
|
const totalHours = totalMinutes / 60;
|
||||||
|
|
||||||
|
if (totalHours >= 24) {
|
||||||
|
const days = Math.floor(totalHours / 24);
|
||||||
|
const remainingHours = totalHours % 24;
|
||||||
|
|
||||||
|
if (remainingHours === 0) {
|
||||||
|
return `${days}天`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留一位小数
|
||||||
|
const displayHours = parseFloat(remainingHours.toFixed(1));
|
||||||
|
return `${days}天${displayHours}小时`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是整数小时,不显示小数点
|
||||||
|
if (Number.isInteger(totalHours)) {
|
||||||
|
return `${totalHours}小时`;
|
||||||
|
}
|
||||||
|
// 保留一位小数,去除末尾的0
|
||||||
|
return `${parseFloat(totalHours.toFixed(1))}小时`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatGameStartTime(startTime: Dayjs) {
|
||||||
|
if (!startTime || !startTime.isValid()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const hour = startTime.hour();
|
||||||
|
const minute = startTime.minute();
|
||||||
|
return minute === 0 ? `${hour}点` : `${hour}点${minute}分`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,10 +14,20 @@ export interface LocationInfo {
|
|||||||
name?: string
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 规范化地址:去掉类似“上海市上海市...”的重复前缀 */
|
||||||
|
export const normalize_address = (address: string): string => {
|
||||||
|
if (!address) return ''
|
||||||
|
// 去空格(包含全角空格)
|
||||||
|
const trimmed = address.replace(/[\s\u3000]+/g, '')
|
||||||
|
// 处理 “xx市xx市...” / “xx省xx省...” 等连续重复
|
||||||
|
// 例:上海市上海市静安区... -> 上海市静安区...
|
||||||
|
return trimmed.replace(/^(.{2,6}?[市省])\1+/, '$1')
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前位置
|
// 获取当前位置
|
||||||
export const getCurrentLocation = (): Promise<LocationInfo> => {
|
export const getCurrentLocation = (): Promise<LocationInfo> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.getLocation({
|
;(Taro as any).getLocation({
|
||||||
type: 'wgs84',
|
type: 'wgs84',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log('===获取地理位置', res)
|
console.log('===获取地理位置', res)
|
||||||
@@ -27,7 +37,7 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
|
|||||||
resolve({
|
resolve({
|
||||||
latitude: res.latitude,
|
latitude: res.latitude,
|
||||||
longitude: res.longitude,
|
longitude: res.longitude,
|
||||||
address
|
address: normalize_address(address)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -48,12 +58,12 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
|
|||||||
// 选择地图位置
|
// 选择地图位置
|
||||||
export const chooseLocation = (): Promise<LocationInfo> => {
|
export const chooseLocation = (): Promise<LocationInfo> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.chooseLocation({
|
;(Taro as any).chooseLocation({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
resolve({
|
resolve({
|
||||||
latitude: res.latitude,
|
latitude: res.latitude,
|
||||||
longitude: res.longitude,
|
longitude: res.longitude,
|
||||||
address: res.address,
|
address: normalize_address(res.address),
|
||||||
name: res.name
|
name: res.name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -66,7 +76,7 @@ export const chooseLocation = (): Promise<LocationInfo> => {
|
|||||||
|
|
||||||
export const getLocation = (): Promise<Location> => {
|
export const getLocation = (): Promise<Location> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.getLocation({
|
;(Taro as any).getLocation({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
resolve({
|
resolve({
|
||||||
latitude: res.latitude,
|
latitude: res.latitude,
|
||||||
|
|||||||
@@ -7,14 +7,40 @@ export function delay(ms: number) {
|
|||||||
export async function payOrder(params) {
|
export async function payOrder(params) {
|
||||||
const { timeStamp, nonceStr, package: _package, signType, paySign } = params;
|
const { timeStamp, nonceStr, package: _package, signType, paySign } = params;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.requestPayment({
|
let settled = false;
|
||||||
timeStamp,
|
const timeout = setTimeout(() => {
|
||||||
nonceStr,
|
if (settled) return;
|
||||||
package: _package,
|
settled = true;
|
||||||
signType,
|
reject(new Error("支付响应超时,请在订单页确认支付结果"));
|
||||||
paySign,
|
}, 20000);
|
||||||
success: resolve,
|
|
||||||
fail: reject.bind(null, new Error("支付失败")),
|
const finish = (cb: () => void) => {
|
||||||
});
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Taro.requestPayment({
|
||||||
|
timeStamp,
|
||||||
|
nonceStr,
|
||||||
|
package: _package,
|
||||||
|
signType,
|
||||||
|
paySign,
|
||||||
|
success: (res) => finish(() => resolve(res)),
|
||||||
|
fail: (err: any) =>
|
||||||
|
finish(() => {
|
||||||
|
const errMsg = String(err?.errMsg || "");
|
||||||
|
if (errMsg.includes("cancel")) {
|
||||||
|
reject(new Error("已取消支付"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(new Error("支付失败,请重试"));
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
finish(() => reject(new Error("支付拉起失败,请重试")));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ const designHeight = 400
|
|||||||
// 获取屏幕宽度,如果没有传入width则使用屏幕宽度
|
// 获取屏幕宽度,如果没有传入width则使用屏幕宽度
|
||||||
const windowWidth = Taro.getSystemInfoSync().windowWidth
|
const windowWidth = Taro.getSystemInfoSync().windowWidth
|
||||||
|
|
||||||
// 获取 DPR - 使用系统像素比确保高清显示
|
// 获取 DPR - 使用系统像素比确保高清显示,限制上限避免内存占用过高
|
||||||
// const systemDpr = Taro.getSystemInfoSync().pixelRatio
|
const systemDpr = Taro.getSystemInfoSync().pixelRatio || 1
|
||||||
const dpr = 1
|
const dpr = Math.min(Math.max(systemDpr, 1), 2)
|
||||||
// Math.min(systemDpr, 3) // 限制最大dpr为3,避免过度放大
|
|
||||||
|
|
||||||
// 2. 计算缩放比例(设备宽度 / 设计稿宽度)
|
// 2. 计算缩放比例(设备宽度 / 设计稿宽度)
|
||||||
const scale = windowWidth / designWidth
|
const scale = windowWidth / designWidth
|
||||||
@@ -88,7 +87,7 @@ const drawLabel = (ctx: any, x: number, y: number, width: number, height: number
|
|||||||
|
|
||||||
// 绘制边框
|
// 绘制边框
|
||||||
ctx.strokeStyle = borderColor
|
ctx.strokeStyle = borderColor
|
||||||
ctx.lineWidth = 1 * dpr
|
ctx.lineWidth = 1
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
// 绘制文字
|
// 绘制文字
|
||||||
@@ -106,19 +105,34 @@ const drawLabel = (ctx: any, x: number, y: number, width: number, height: number
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 给图片 URL 加随机参数,避免同一链接二次加载不触发 onload */
|
||||||
|
function with_cache_bust(url: string): string {
|
||||||
|
const sep = url.includes('?') ? '&' : '?';
|
||||||
|
return `${url}${sep}_t=${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// 工具函数 - OffscreenCanvas 下加载图片(使用 offscreen.createImage)
|
// 工具函数 - OffscreenCanvas 下加载图片(使用 offscreen.createImage)
|
||||||
const loadImage = (src: string): Promise<any> => {
|
const loadImage = (src: string): Promise<any> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null
|
||||||
try {
|
try {
|
||||||
const off = runtime.offscreen
|
const off = runtime.offscreen
|
||||||
if (!off || typeof off.createImage !== 'function') {
|
if (!off || typeof off.createImage !== 'function') {
|
||||||
throw new Error('OffscreenCanvas 未初始化或不支持 createImage')
|
throw new Error('OffscreenCanvas 未初始化或不支持 createImage')
|
||||||
}
|
}
|
||||||
const img = off.createImage()
|
const img = off.createImage()
|
||||||
img.onload = () => resolve(img)
|
timer = setTimeout(() => reject(new Error(`图片加载超时: ${src}`)), 8000)
|
||||||
img.onerror = reject
|
img.onload = () => {
|
||||||
img.src = src
|
if (timer) clearTimeout(timer)
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
img.onerror = (e: any) => {
|
||||||
|
if (timer) clearTimeout(timer)
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
img.src = with_cache_bust(src)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (timer) clearTimeout(timer)
|
||||||
reject(e)
|
reject(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -128,14 +142,14 @@ const loadImage = (src: string): Promise<any> => {
|
|||||||
const drawSVGPathToCanvas = (ctx: any) => {
|
const drawSVGPathToCanvas = (ctx: any) => {
|
||||||
// 设置绘制样式
|
// 设置绘制样式
|
||||||
ctx.strokeStyle = '#00E5AD';
|
ctx.strokeStyle = '#00E5AD';
|
||||||
ctx.lineWidth = scale * 3 * dpr;
|
ctx.lineWidth = scale * 3;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
// 移动到指定位置并缩放
|
// 移动到指定位置并缩放
|
||||||
ctx.translate(scale * 200 * dpr, scale * 90 * dpr);
|
ctx.translate(scale * 200, scale * 90);
|
||||||
const scaleValue = 0.8
|
const scaleValue = 0.8
|
||||||
ctx.scale(scaleValue, scaleValue);
|
ctx.scale(scaleValue, scaleValue);
|
||||||
|
|
||||||
@@ -358,43 +372,36 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
console.log('开始绘制分享卡片...')
|
console.log('开始绘制分享卡片...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置Canvas的实际尺寸(使用dpr确保高清显示)
|
// 统一逻辑坐标:先按 dpr 缩放,再使用设计坐标绘制
|
||||||
const canvasWidthPx = canvasWidth * dpr
|
const canvasWidthPx = canvasWidth * dpr
|
||||||
const canvasHeightPx = canvasHeight * dpr
|
const canvasHeightPx = canvasHeight * dpr
|
||||||
|
|
||||||
// 清空画布
|
|
||||||
ctx.clearRect(0, 0, canvasWidthPx, canvasHeightPx)
|
ctx.clearRect(0, 0, canvasWidthPx, canvasHeightPx)
|
||||||
|
ctx.save()
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
console.log('画布已清空')
|
console.log('画布已清空')
|
||||||
|
|
||||||
// 如果dpr大于2,进行缩放处理以避免内容过大
|
|
||||||
if (dpr > 2) {
|
|
||||||
const scale = 2 / dpr
|
|
||||||
ctx.scale(scale, scale)
|
|
||||||
console.log('应用缩放:', scale)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制背景 - 渐变色 已完成
|
// 绘制背景 - 渐变色 已完成
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeightPx)
|
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight)
|
||||||
gradient.addColorStop(0, '#BFFFEF')
|
gradient.addColorStop(0, '#BFFFEF')
|
||||||
gradient.addColorStop(1, '#F2FFFC')
|
gradient.addColorStop(1, '#F2FFFC')
|
||||||
ctx.fillStyle = gradient
|
ctx.fillStyle = gradient
|
||||||
ctx.fillRect(0, 0, canvasWidthPx, canvasHeightPx)
|
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
|
||||||
console.log('背景绘制完成')
|
console.log('背景绘制完成')
|
||||||
|
|
||||||
// 绘制背景条纹 已完成
|
// 绘制背景条纹 已完成
|
||||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.03)'
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.03)'
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2
|
||||||
for (let i = 0; i < canvasWidthPx; i += 4) {
|
for (let i = 0; i < canvasWidth; i += 4) {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(i, 0)
|
ctx.moveTo(i, 0)
|
||||||
ctx.lineTo(i, canvasHeightPx)
|
ctx.lineTo(i, canvasHeight)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制用户头像(左上角) 已完成
|
// 绘制用户头像(左上角) 已完成
|
||||||
const avatarSize = scale * 32 * dpr // 32px * dpr
|
const avatarSize = scale * 32
|
||||||
const avatarX = scale * 35 * dpr // 距离左侧35px
|
const avatarX = scale * 35
|
||||||
const avatarY = scale * 35 * dpr // 距离顶部35px
|
const avatarY = scale * 35
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const avatarPath = await loadImage(data.userAvatar)
|
const avatarPath = await loadImage(data.userAvatar)
|
||||||
@@ -407,29 +414,29 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 如果头像加载失败,绘制默认头像
|
// 如果头像加载失败,绘制默认头像
|
||||||
ctx.setFillStyle('#CCCCCC')
|
ctx.fillStyle = '#CCCCCC'
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
|
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制用户昵称 已完成
|
// 绘制用户昵称 已完成
|
||||||
const nicknameX = avatarX + avatarSize + 8 * dpr // 距离头像8px
|
const nicknameX = avatarX + avatarSize + scale * 8
|
||||||
const nicknameY = avatarY + (avatarSize - 18 * dpr) / 2 // 与头像水平居中对齐
|
const nicknameY = avatarY + (avatarSize - scale * 18) / 2
|
||||||
const nicknameFontSize = scale * 18 * dpr
|
const nicknameFontSize = scale * 18
|
||||||
drawBoldText(ctx, data.userNickname, nicknameX, nicknameY, nicknameFontSize, '#000000', 'Noto Sans SC', '900')
|
drawBoldText(ctx, data.userNickname, nicknameX, nicknameY, nicknameFontSize, '#000000', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 绘制"邀你加入球局"文案
|
// 绘制"邀你加入球局"文案
|
||||||
const inviteX = scale * 35 * dpr // 距离画布左侧35px
|
const inviteX = scale * 35
|
||||||
const inviteY = scale * 100 * dpr // 距离画布顶部79px
|
const inviteY = scale * 100
|
||||||
const inviteFontSize = scale * 44 * dpr
|
const inviteFontSize = scale * 44
|
||||||
|
|
||||||
// 绘制"邀你加入"
|
// 绘制"邀你加入"
|
||||||
drawBoldText(ctx, '邀你加入', inviteX, inviteY, inviteFontSize, '#000000', 'Noto Sans SC', '900')
|
drawBoldText(ctx, '邀你加入', inviteX, inviteY, inviteFontSize, '#000000', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 绘制"球局"特殊样式
|
// 绘制"球局"特殊样式
|
||||||
const qiuJuX = inviteX + ctx.measureText('邀你加入').width + 4 * dpr
|
const qiuJuX = inviteX + ctx.measureText('邀你加入').width + scale * 4
|
||||||
const qiuJuFontSize = scale * 44 * dpr
|
const qiuJuFontSize = scale * 44
|
||||||
drawBoldText(ctx, '球局', qiuJuX, inviteY, qiuJuFontSize, '#00E5AD', 'Noto Sans SC', '900')
|
drawBoldText(ctx, '球局', qiuJuX, inviteY, qiuJuFontSize, '#00E5AD', 'Noto Sans SC', '900')
|
||||||
|
|
||||||
// 测试绘制网络图片
|
// 测试绘制网络图片
|
||||||
@@ -437,12 +444,12 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
|
|
||||||
// 绘制球员图片(右上角)已完成
|
// 绘制球员图片(右上角)已完成
|
||||||
let venueBaseConfig = {
|
let venueBaseConfig = {
|
||||||
venueImgX: scale * 340 * dpr,
|
venueImgX: scale * 340,
|
||||||
venueImgY: scale * 35 * dpr,
|
venueImgY: scale * 35,
|
||||||
rotation: scale * -8, // 旋转-8度
|
rotation: scale * -8, // 旋转-8度
|
||||||
venueImgSize: scale * 124 * dpr,
|
venueImgSize: scale * 124,
|
||||||
borderRadius: scale * 24 * dpr,
|
borderRadius: scale * 24,
|
||||||
padding: scale * 4 * dpr,
|
padding: scale * 4,
|
||||||
venueImage: data.venueImages?.[0]
|
venueImage: data.venueImages?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,8 +458,8 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
const venueBackConfig = {
|
const venueBackConfig = {
|
||||||
...venueBaseConfig,
|
...venueBaseConfig,
|
||||||
venueImage: data.venueImages?.[1],
|
venueImage: data.venueImages?.[1],
|
||||||
venueImgX: scale * 400 * dpr,
|
venueImgX: scale * 400,
|
||||||
venueImgY: scale * 35 * dpr,
|
venueImgY: scale * 35,
|
||||||
rotation: scale * -10, // 旋转-10度
|
rotation: scale * -10, // 旋转-10度
|
||||||
}
|
}
|
||||||
await drawVenueImages(ctx, venueBackConfig)
|
await drawVenueImages(ctx, venueBackConfig)
|
||||||
@@ -487,11 +494,11 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
// 绘制"单打"标签
|
// 绘制"单打"标签
|
||||||
const danDaX = scale * 100
|
const danDaX = scale * 100
|
||||||
const danDaY = scale * 196
|
const danDaY = scale * 196
|
||||||
const danDaHeight = scale * 40 * dpr
|
const danDaHeight = scale * 40
|
||||||
const danDaRadius = scale * 20 * dpr
|
const danDaRadius = scale * 20
|
||||||
const danDaFontSize = scale * 22 * dpr
|
const danDaFontSize = scale * 22
|
||||||
// 根据内容动态计算标签宽度(左右内边距)
|
// 根据内容动态计算标签宽度(左右内边距)
|
||||||
const danDaPaddingX = scale * 16 * dpr
|
const danDaPaddingX = scale * 16
|
||||||
setFont2D(ctx, danDaFontSize)
|
setFont2D(ctx, danDaFontSize)
|
||||||
const danDaTextWidth = ctx.measureText(data.gameType).width
|
const danDaTextWidth = ctx.measureText(data.gameType).width
|
||||||
const danDaWidth = danDaTextWidth + danDaPaddingX * 2
|
const danDaWidth = danDaTextWidth + danDaPaddingX * 2
|
||||||
@@ -502,11 +509,11 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
const labelGap = scale * 16 // 两个标签之间的间距(不乘 dpr,保持视觉间距)
|
const labelGap = scale * 16 // 两个标签之间的间距(不乘 dpr,保持视觉间距)
|
||||||
const skillX = danDaX + danDaWidth + labelGap
|
const skillX = danDaX + danDaWidth + labelGap
|
||||||
const skillY = scale * 196
|
const skillY = scale * 196
|
||||||
const skillHeight = scale * 40 * dpr
|
const skillHeight = scale * 40
|
||||||
const skillRadius = scale * 20 * dpr
|
const skillRadius = scale * 20
|
||||||
const skillFontSize = scale * 22 * dpr
|
const skillFontSize = scale * 22
|
||||||
// 根据内容动态计算技能标签宽度
|
// 根据内容动态计算技能标签宽度
|
||||||
const skillPaddingX = scale * 20 * dpr
|
const skillPaddingX = scale * 20
|
||||||
setFont2D(ctx, skillFontSize)
|
setFont2D(ctx, skillFontSize)
|
||||||
const skillTextWidth = ctx.measureText(data.skillLevel).width
|
const skillTextWidth = ctx.measureText(data.skillLevel).width
|
||||||
const skillWidth = skillTextWidth + skillPaddingX * 2
|
const skillWidth = skillTextWidth + skillPaddingX * 2
|
||||||
@@ -516,7 +523,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
// 绘制日期时间
|
// 绘制日期时间
|
||||||
const dateX = danDaX
|
const dateX = danDaX
|
||||||
const timeInfoY = infoStartY + infoSpacing
|
const timeInfoY = infoStartY + infoSpacing
|
||||||
const timeInfoFontSize = scale * 24 * dpr
|
const timeInfoFontSize = scale * 24
|
||||||
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`)
|
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`)
|
||||||
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
@@ -524,15 +531,16 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
drawBoldText(ctx, data.gameDate, dateX, timeInfoY + 8, timeInfoFontSize, '#00E5AD')
|
drawBoldText(ctx, data.gameDate, dateX, timeInfoY + 8, timeInfoFontSize, '#00E5AD')
|
||||||
|
|
||||||
// 绘制时间(黑色)
|
// 绘制时间(黑色)
|
||||||
const timeX = textX + ctx.measureText(data.gameDate).width + 10 * dpr
|
const timeX = textX + ctx.measureText(data.gameDate).width + scale * 10
|
||||||
drawBoldText(ctx, data.gameTime, timeX, timeInfoY + 8, timeInfoFontSize, '#000000')
|
drawBoldText(ctx, data.gameTime, timeX, timeInfoY + 8, timeInfoFontSize, '#000000')
|
||||||
|
|
||||||
// 绘制地点
|
// 绘制地点
|
||||||
const locationInfoY = infoStartY + infoSpacing * 2
|
const locationInfoY = infoStartY + infoSpacing * 2
|
||||||
const locationFontSize = scale * 22 * dpr
|
const locationFontSize = scale * 22
|
||||||
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`)
|
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`)
|
||||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||||
|
ctx.restore()
|
||||||
try {
|
try {
|
||||||
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
||||||
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
||||||
@@ -588,10 +596,12 @@ export async function generateShareImage(data: ShareCardData): Promise<string> {
|
|||||||
// 记录到 runtime(供 loadImage 使用)
|
// 记录到 runtime(供 loadImage 使用)
|
||||||
runtime.offscreen = offscreen
|
runtime.offscreen = offscreen
|
||||||
isDrawing = true
|
isDrawing = true
|
||||||
|
try {
|
||||||
const imagePath = await drawShareCard(ctx, data, offscreen)
|
const imagePath = await drawShareCard(ctx, data, offscreen)
|
||||||
isDrawing = false
|
return imagePath
|
||||||
return imagePath
|
} finally {
|
||||||
|
isDrawing = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default generateShareImage
|
export default generateShareImage
|
||||||
|
|||||||
Reference in New Issue
Block a user