19 Commits

Author SHA1 Message Date
李瑞
99c8026f61 数据为空时允许展示banner 2026-02-11 23:13:49 +08:00
05966b2acb 优化ntrp和性别picker偶尔选不上值 2026-02-10 18:00:26 +08:00
张成
4cf2b959b5 1 2026-02-10 12:42:42 +08:00
张成
43610dcf99 修复首页数据少的问题 2026-02-10 11:46:39 +08:00
张成
05aa820466 Merge branch 'master' of https://git.bimwe.com/bimwe/mini-programs 2026-02-09 22:03:59 +08:00
筱野
b154e31f8f Merge branch 'master' of https://git.bimwe.com/bimwe/mini-programs 2026-02-09 22:03:09 +08:00
筱野
669ee2fe4e 解决按钮问题与键盘弹出问题 2026-02-09 22:03:01 +08:00
张成
281ee2b746 Merge branch 'master' of https://git.bimwe.com/bimwe/mini-programs 2026-02-09 22:02:46 +08:00
张成
132c74d27c 1 2026-02-09 22:02:43 +08:00
李瑞
6b6a4c9480 替换玩法icon 2026-02-09 22:01:36 +08:00
筱野
0f8dd44f5a 解决按钮问题与键盘弹出问题 2026-02-09 21:16:48 +08:00
82ba753b8b Merge remote-tracking branch 'refs/remotes/origin/master' 2026-02-09 20:07:47 +08:00
159d81ed12 fix: 详情管理功能按钮逻辑修改 2026-02-09 20:07:05 +08:00
22965eedf3 个人简介和昵称修改后显示底部导航 2026-02-09 16:36:15 +08:00
49935dd049 优化省市区和占位图片 2026-02-09 13:53:19 +08:00
张成
cab90aa1cb 1 2026-02-09 13:25:13 +08:00
632da5112d feat: 删除小猫 2026-02-09 09:49:43 +08:00
28955e9da1 fix: 修改分享初始化逻辑、去除小猫图 2026-02-08 23:58:37 +08:00
70a66fabdc Merge branch 'feat/liujie' 2026-02-08 22:57:48 +08:00
32 changed files with 577 additions and 299 deletions

2
.gitignore vendored
View File

@@ -8,4 +8,6 @@ node_modules/
src/config/env.ts src/config/env.ts
.vscode .vscode
*.http *.http
.cursor
.codewiz

View File

@@ -4,7 +4,11 @@ export default {
quiet: false, quiet: false,
stats: true stats: true
}, },
mini: {}, mini: {
webpackChain(chain) {
chain.devtool('source-map')
}
},
h5: {}, h5: {},
// 添加这个配置来显示完整错误信息 // 添加这个配置来显示完整错误信息
compiler: { compiler: {

128
config/env.ts Normal file
View File

@@ -0,0 +1,128 @@
import Taro from '@tarojs/taro'
// 环境类型
export type EnvType = 'development' | 'production'
// 环境配置接口
export interface EnvConfig {
name: string
apiBaseURL: string
timeout: number
enableLog: boolean
enableMock: boolean
// 客服配置
customerService: {
corpId: string
serviceUrl: string
phoneNumber?: string
email?: string
}
}
// 各环境配置
const envConfigs: Record<EnvType, EnvConfig> = {
// 开发环境
development: {
name: '开发环境',
// apiBaseURL: 'https://tennis.bimwe.com',
apiBaseURL: 'http://localhost:9098',
timeout: 15000,
enableLog: true,
enableMock: false,
// 客服配置
customerService: {
corpId: 'ww51fc969e8b76af82', // 企业ID
serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
}
},
// 生产环境1
// production: {
// name: '生产环境1',
// apiBaseURL: 'https://tennis.bimwe.com',
// timeout: 10000,
// enableLog: false,
// enableMock: false,
// // 客服配置
// customerService: {
// corpId: 'ww51fc969e8b76af82', // 企业ID
// serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
// }
// },
// 生产环境2
production: {
name: '生产环境2',
apiBaseURL: 'https://youchang.qiongjingtiyu.com',
timeout: 10000,
enableLog: false,
enableMock: false,
// 客服配置
customerService: {
corpId: 'ww9a2d9a5d9410c664', // 企业ID
serviceUrl: 'https://work.weixin.qq.com/kfid/kfcd355e162e0390684',
}
}
}
// 获取当前环境
export const getCurrentEnv = (): EnvType => {
// 在小程序环境中,使用默认逻辑判断环境
// 可以根据实际需要配置不同的判断逻辑
// 可以根据实际部署情况添加更多判断逻辑
// 比如通过 Taro.getEnv() 获取当前平台环境
const isProd = process.env.NODE_ENV === 'production'
if (isProd) {
return 'production'
} else {
return 'development'
}
}
// 获取当前环境配置
export const getCurrentConfig = (): EnvConfig => {
const env = getCurrentEnv()
return envConfigs[env]
}
// 获取指定环境配置
export const getEnvConfig = (env: EnvType): EnvConfig => {
return envConfigs[env]
}
// 是否为开发环境
export const isDevelopment = (): boolean => {
return getCurrentEnv() === 'development'
}
// 是否为生产环境
export const isProduction = (): boolean => {
return getCurrentEnv() === 'production'
}
// 环境配置调试信息
export const getEnvInfo = () => {
const config = getCurrentConfig()
return {
env: getCurrentEnv(),
config,
taroEnv: Taro.getEnv(),
platform: Taro.getEnv() === Taro.ENV_TYPE.WEAPP ? '微信小程序' :
Taro.getEnv() === Taro.ENV_TYPE.WEB ? 'Web' :
Taro.getEnv() === Taro.ENV_TYPE.RN ? 'React Native' : '未知'
}
}
// 导出当前环境配置(方便直接使用)
export default getCurrentConfig()

View File

@@ -3,6 +3,12 @@
.common-popup { .common-popup {
position: fixed; position: fixed;
z-index: 9999 !important; z-index: 9999 !important;
padding: 0;
box-sizing: border-box;
max-height: calc(100vh - 10px);
display: flex;
flex-direction: column;
background-color: theme.$page-background-color;
&:global(.nut-popup-bottom.nut-popup-round) { &:global(.nut-popup-bottom.nut-popup-round) {
border-radius: 20px 20px 0 0 !important; border-radius: 20px 20px 0 0 !important;
} }
@@ -32,12 +38,7 @@
} }
} }
padding: 0;
box-sizing: border-box;
max-height: calc(100vh - 10px);
display: flex;
flex-direction: column;
background-color: theme.$page-background-color;
// .common-popup__header { // .common-popup__header {
// padding: 12px 16px; // padding: 12px 16px;

View File

@@ -48,7 +48,14 @@ const CustomPopup: React.FC<CustomPopupProps> = ({
const touchStartY = useRef(0) const touchStartY = useRef(0)
// 使用全局键盘状态 // 使用全局键盘状态
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight() const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardHeight()
// 当弹窗显示时,设置键盘为不可见
useEffect(() => {
if (visible) {
setKeyboardVisible(false)
}
}, [visible, setKeyboardVisible])
// 使用全局键盘状态监听 // 使用全局键盘状态监听
useEffect(() => { useEffect(() => {

View File

@@ -189,7 +189,8 @@ export default forwardRef(function GameManagePopup(props, ref) {
detail.match_status, detail.match_status,
); );
const inTwoHours = dayjs(detail.start_time).diff(dayjs(), "hour") < 2; // const inTwoHours = dayjs(detail.start_time).diff(dayjs(), "hour") < 2;
const beforeStart = dayjs(detail.start_time).isAfter(dayjs());
const hasOtherParticiappants = (detail.participants || []) const hasOtherParticiappants = (detail.participants || [])
.filter((item) => item.status === "joined") .filter((item) => item.status === "joined")
@@ -207,7 +208,7 @@ export default forwardRef(function GameManagePopup(props, ref) {
style={{ minHeight: "unset" }} style={{ minHeight: "unset" }}
> >
<View className={styles.container}> <View className={styles.container}>
{!finished && !inTwoHours && !hasOtherParticiappants && ( {!finished && !hasOtherParticiappants && beforeStart && (
<View className={styles.button} onClick={handleEditGame}> <View className={styles.button} onClick={handleEditGame}>
</View> </View>
@@ -217,12 +218,12 @@ export default forwardRef(function GameManagePopup(props, ref) {
</View> </View>
)} )}
{!finished && !inTwoHours && !hasOtherParticiappants && ( {!finished && beforeStart && (
<View className={styles.button} onClick={handleCancelGame}> <View className={styles.button} onClick={handleCancelGame}>
</View> </View>
)} )}
{!finished && hasJoin && ( {!finished && beforeStart && hasJoin && (
<View className={styles.button} onClick={handleQuitGame}> <View className={styles.button} onClick={handleQuitGame}>
退 退
</View> </View>

View File

@@ -15,7 +15,7 @@ const GamePlayType = (props: IProps) => {
const { name, onChange, value, options } = props; const { name, onChange, value, options } = props;
return ( return (
<View className={styles.gamePlayWrapper}> <View className={styles.gamePlayWrapper}>
<TitleComponent title="玩法" icon={<Image src={img.ICON_SITE} />} /> <TitleComponent title="玩法" icon={<Image src={img.ICON_GAME_PLAY} />} />
<Bubble <Bubble
options={options} options={options}
value={value} value={value}

View File

@@ -5,8 +5,9 @@ import img from "../../config/images";
import { ListCardProps } from "../../../types/list/types"; import { ListCardProps } from "../../../types/list/types";
import { formatGameTime, calculateDuration } from "@/utils/timeUtils"; import { formatGameTime, calculateDuration } from "@/utils/timeUtils";
import { navigateTo } from "@/utils/navigation"; import { navigateTo } from "@/utils/navigation";
import images from '@/config/images' import images from "@/config/images";
import "./index.scss"; import "./index.scss";
import { OSS_BASE } from "@/config/api";
const ListCard: React.FC<ListCardProps> = ({ const ListCard: React.FC<ListCardProps> = ({
id, id,
@@ -45,7 +46,7 @@ const ListCard: React.FC<ListCardProps> = ({
className="image" className="image"
mode="aspectFill" mode="aspectFill"
lazyLoad lazyLoad
defaultSource={require("@/static/emptyStatus/publish-empty-card.png")} defaultSource={`${OSS_BASE}/front/ball/images/publish-empty-card.svg`}
/> />
); );
}; };
@@ -67,7 +68,9 @@ const ListCard: React.FC<ListCardProps> = ({
const containerWidthPx = screenWidth - 130; const containerWidthPx = screenWidth - 130;
// 计算固定信息宽度 // 计算固定信息宽度
const extraInfo = `${court_type ? `${court_type}` : ''}${distance_km ? `${distance_km}km` : ''}`; const extraInfo = `${court_type ? `${court_type}` : ""}${
distance_km ? `${distance_km}km` : ""
}`;
// 估算字符宽度(基于 12px 字体) // 估算字符宽度(基于 12px 字体)
const getTextWidth = (text: string) => { const getTextWidth = (text: string) => {
@@ -98,7 +101,9 @@ const ListCard: React.FC<ListCardProps> = ({
let currentWidth = 0; let currentWidth = 0;
for (let i = 0; i < location.length; i++) { for (let i = 0; i < location.length; i++) {
const char = location[i]; const char = location[i];
const charWidth = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char) ? 12 : 6; const charWidth = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char)
? 12
: 6;
if (currentWidth + charWidth > availableWidth) { if (currentWidth + charWidth > availableWidth) {
break; break;
} }
@@ -106,7 +111,7 @@ const ListCard: React.FC<ListCardProps> = ({
maxChars++; maxChars++;
} }
return location.slice(0, maxChars) + '...'; return location.slice(0, maxChars) + "...";
}, [location, court_type, distance_km]); }, [location, court_type, distance_km]);
// 根据图片数量决定展示样式 // 根据图片数量决定展示样式
@@ -220,9 +225,10 @@ const ListCard: React.FC<ListCardProps> = ({
</Text> </Text>
</View> </View>
<View className="tag ntprTag"> <View className="tag ntprTag">
<Image src={images.ICON_LIST_NTPR} className='ntprIcon' /> <Image src={images.ICON_LIST_NTPR} className="ntprIcon" />
<Text className="tag-text"> <Text className="tag-text">
{Number(skill_level_min)?.toFixed(1)} - {Number(skill_level_max)?.toFixed(1)} {Number(skill_level_min)?.toFixed(1)} -{" "}
{Number(skill_level_max)?.toFixed(1)}
</Text> </Text>
{/* 分割线 */} {/* 分割线 */}
<View className="typeLine" /> <View className="typeLine" />
@@ -251,13 +257,8 @@ const ListCard: React.FC<ListCardProps> = ({
/> />
{/* <Text className="smoothTitle">{game_type}</Text> */} {/* <Text className="smoothTitle">{game_type}</Text> */}
</View> </View>
{ {venue_description && <View className="line" />}
venue_description && (<View className="line" />) {venue_description && (
}
{
venue_description &&
(
<View className="localAreaContainer"> <View className="localAreaContainer">
<View className="localAreaTitle">:</View> <View className="localAreaTitle">:</View>
<View className="localAreaWrapper"> <View className="localAreaWrapper">
@@ -265,8 +266,7 @@ const ListCard: React.FC<ListCardProps> = ({
<Text className="localAreaText">{venue_description}</Text> <Text className="localAreaText">{venue_description}</Text>
</View> </View>
</View> </View>
) )}
}
</View> </View>
)} )}
</View> </View>

View File

@@ -24,7 +24,6 @@ const ListLoadError = (props: IProps) => {
wrapperHeight = "", wrapperHeight = "",
width = "", width = "",
height = "", height = "",
scale = "",
} = props; } = props;
const handleReload = () => { const handleReload = () => {
reload && typeof reload === "function" && reload(); reload && typeof reload === "function" && reload();
@@ -34,7 +33,7 @@ const ListLoadError = (props: IProps) => {
<View className={styles.listLoadError} style={{ height: wrapperHeight }}> <View className={styles.listLoadError} style={{ height: wrapperHeight }}>
<Image <Image
className={styles.listLoadErrorImg} className={styles.listLoadErrorImg}
style={{ width, height, transform: `scale(${scale})` }} style={{ width, height }}
src={errorImg ? img[errorImg] : img.ICON_LIST_LOAD_ERROR} src={errorImg ? img[errorImg] : img.ICON_LIST_LOAD_ERROR}
/> />
{text && <Text className={styles.listLoadErrorText}>{text}</Text>} {text && <Text className={styles.listLoadErrorText}>{text}</Text>}

View File

@@ -52,7 +52,7 @@ const PopupPicker = ({
ntrpTested, ntrpTested,
}: PickerProps) => { }: PickerProps) => {
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]); const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([]); const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([...options]);
const [pickerCurrentValue, setPickerCurrentValue] = const [pickerCurrentValue, setPickerCurrentValue] =
useState<(string | number)[]>(value); useState<(string | number)[]>(value);

View File

@@ -6,7 +6,12 @@ import "./index.scss";
import { EditModal } from "@/components"; import { EditModal } from "@/components";
import { UserService, PickerOption } from "@/services/userService"; import { UserService, PickerOption } from "@/services/userService";
import { PopupPicker } from "@/components/Picker/index"; import { PopupPicker } from "@/components/Picker/index";
import { useUserActions, useNicknameChangeStatus, useLastTestResult } from "@/store/userStore"; import {
useUserActions,
useNicknameChangeStatus,
useLastTestResult,
useUserInfo,
} from "@/store/userStore";
import { UserInfoType } from "@/services/userService"; import { UserInfoType } from "@/services/userService";
import { import {
useCities, useCities,
@@ -69,7 +74,6 @@ const on_edit = () => {
// 用户信息卡片组件 // 用户信息卡片组件
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
editable = true, editable = true,
user_info,
is_current_user, is_current_user,
is_following = false, is_following = false,
collapseProfile, collapseProfile,
@@ -80,9 +84,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
set_user_info, set_user_info,
onTab, onTab,
}) => { }) => {
const user_info = useUserInfo();
const nickname_change_status = useNicknameChangeStatus(); const nickname_change_status = useNicknameChangeStatus();
const { setShowGuideBar } = useGlobalState(); const { setShowGuideBar } = useGlobalState();
const { updateUserInfo, updateNickname, fetchLastTestResult } = useUserActions(); const { updateUserInfo, updateNickname, fetchLastTestResult } =
useUserActions();
const ntrpLevels = useNtrpLevels(); const ntrpLevels = useNtrpLevels();
// 使用全局状态中的测试结果,避免重复调用接口 // 使用全局状态中的测试结果,避免重复调用接口
const lastTestResult = useLastTestResult(); const lastTestResult = useLastTestResult();
@@ -117,11 +124,15 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
useState(false); useState(false);
// 表单状态 // 表单状态
const [form_data, set_form_data] = useState<Partial<UserInfoType>>({}); const [form_data, set_form_data] = useState<Partial<UserInfoType>>({ ...user_info });
useDidShow(() => { // useDidShow(() => {
// set_form_data({ ...user_info });
// });
useEffect(() => {
set_form_data({ ...user_info }); set_form_data({ ...user_info });
}); }, [user_info])
useEffect(() => { useEffect(() => {
const visibles = [ const visibles = [
@@ -129,6 +140,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
location_picker_visible, location_picker_visible,
ntrp_picker_visible, ntrp_picker_visible,
occupation_picker_visible, occupation_picker_visible,
edit_modal_visible,
]; ];
const allPickersClosed = visibles.every((item) => !item); const allPickersClosed = visibles.every((item) => !item);
// 所有选择器都关闭时,显示 GuideBar否则隐藏 // 所有选择器都关闭时,显示 GuideBar否则隐藏
@@ -138,6 +150,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
location_picker_visible, location_picker_visible,
ntrp_picker_visible, ntrp_picker_visible,
occupation_picker_visible, occupation_picker_visible,
edit_modal_visible,
]); ]);
// 职业数据 // 职业数据
@@ -295,8 +308,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
// 处理地区选择 // 处理地区选择
const handle_location_change = (e: any) => { const handle_location_change = (e: any) => {
const [country, province, city] = e; const [province, city, district] = e;
handle_field_edit({ country, province, city }); handle_field_edit({ province, city, district });
}; };
// 处理NTRP水平选择 // 处理NTRP水平选择
@@ -307,8 +320,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
// 处理职业选择 // 处理职业选择
const handle_occupation_change = (e: any) => { const handle_occupation_change = (e: any) => {
const [country, province, city] = e; const [firstVal, secondVal, thirdVal] = e;
handle_field_edit("occupation", `${country} ${province} ${city}`); handle_field_edit("occupation", `${firstVal} ${secondVal} ${thirdVal}`);
}; };
const handle_edit_modal_cancel = () => { const handle_edit_modal_cancel = () => {
// 关闭编辑弹窗时显示 GuideBar // 关闭编辑弹窗时显示 GuideBar
@@ -365,7 +378,6 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
urls: [url], urls: [url],
}); });
}; };
return ( return (
<View className="user_info_card"> <View className="user_info_card">
{/* 头像和基本信息 */} {/* 头像和基本信息 */}
@@ -565,12 +577,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
<Text></Text> <Text></Text>
</View> </View>
) : null} ) : null}
{user_info.country || user_info.province || user_info.city ? ( {user_info.province || user_info.city || user_info.district ? (
<View <View
className="tag_item" className="tag_item"
onClick={() => editable && handle_open_edit_modal("location")} onClick={() => editable && handle_open_edit_modal("location")}
> >
<Text className="tag_text">{`${user_info.province}${user_info.city}`}</Text> <Text className="tag_text">{`${user_info.city}${user_info.district}`}</Text>
</View> </View>
) : is_current_user ? ( ) : is_current_user ? (
<View <View
@@ -643,16 +655,16 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
<PopupPicker <PopupPicker
showHeader={true} showHeader={true}
title="选择性别" title="选择性别"
options={[ options={
[ [
{ text: "男", value: "0" }, { text: "男", value: "0" },
{ text: "女", value: "1" }, { text: "女", value: "1" },
{ text: "保密", value: "2" }, { text: "保密", value: "2" },
], ]
]} }
visible={gender_picker_visible} visible={gender_picker_visible}
setvisible={setGenderPickerVisible} setvisible={setGenderPickerVisible}
value={form_data.gender === "" ? ["0"] : [form_data.gender]} value={!form_data.gender ? ["0"] : [form_data.gender]}
onChange={handle_gender_change} onChange={handle_gender_change}
/> />
)} )}
@@ -665,8 +677,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
visible={location_picker_visible} visible={location_picker_visible}
setvisible={setLocationPickerVisible} setvisible={setLocationPickerVisible}
value={ value={
form_data.country form_data.province
? [form_data.country, form_data.province, form_data.city] ? [form_data.province, form_data.city, form_data.district]
: getDefaultOption(cities) : getDefaultOption(cities)
} }
onChange={handle_location_change} onChange={handle_location_change}
@@ -678,15 +690,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
showHeader={true} showHeader={true}
title="选择 NTRP 自评水平" title="选择 NTRP 自评水平"
ntrpTested={ntrpTested} ntrpTested={ntrpTested}
options={ntrpLevels.map((level) => ({ options={ntrpLevels}
text: level,
value: level,
}))}
type="ntrp" type="ntrp"
img={user_info.avatar_url || ""} img={user_info.avatar_url || ""}
visible={ntrp_picker_visible} visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible} setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level || "2.5"]} value={!form_data.ntrp_level ? ["2.5"] : [form_data.ntrp_level]}
onChange={handle_ntrp_level_change} onChange={handle_ntrp_level_change}
/> />
)} )}
@@ -864,8 +873,7 @@ export const GameTabs: React.FC<GameTabsProps> = ({
<Text className="tab_text">{hosted_text}</Text> <Text className="tab_text">{hosted_text}</Text>
</View> </View>
<View <View
className={`tab_item ${ className={`tab_item ${active_tab === "participated" ? "active" : ""
active_tab === "participated" ? "active" : ""
}`} }`}
onClick={() => on_tab_change("participated")} onClick={() => on_tab_change("participated")}
> >

View File

@@ -1,74 +1,76 @@
import { OSS_BASE } from "@/config/api";
export default { export default {
ICON_REMOVE: require('@/static/publishBall/icon-remove.svg'), ICON_REMOVE: require("@/static/publishBall/icon-remove.svg"),
ICON_UPLOAD: require('@/static/publishBall/icon-upload.svg'), ICON_UPLOAD: require("@/static/publishBall/icon-upload.svg"),
ICON_LOCATION: require('@/static/publishBall/icon-location.svg'), ICON_LOCATION: require("@/static/publishBall/icon-location.svg"),
ICON_GAMEPLAY: require('@/static/publishBall/icon-gameplay.svg'), ICON_GAMEPLAY: require("@/static/publishBall/icon-gameplay.svg"),
ICON_PERSONAL: require('@/static/publishBall/icon-personal.svg'), ICON_PERSONAL: require("@/static/publishBall/icon-personal.svg"),
ICON_CHANGDA: require('@/static/publishBall/icon-changda.svg'), ICON_CHANGDA: require("@/static/publishBall/icon-changda.svg"),
ICON_COST: require('@/static/publishBall/icon-cost.svg'), ICON_COST: require("@/static/publishBall/icon-cost.svg"),
ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_TIPS: require("@/static/publishBall/icon-tips.svg"),
ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), ICON_ARROW_RIGHT: require("@/static/publishBall/icon-arrow-right.svg"),
ICON_FILTER: require('@/static/list/icon-filter.svg'), ICON_FILTER: require("@/static/list/icon-filter.svg"),
ICON_FILTER_SELECTED: require('@/static/list/icon-filter-selected.svg'), ICON_FILTER_SELECTED: require("@/static/list/icon-filter-selected.svg"),
ICON_SEARCH: require('@/static/list/icon-search.svg'), ICON_SEARCH: require("@/static/list/icon-search.svg"),
ICON_PLAY: require('@/static/list/icon-play.svg'), ICON_PLAY: require("@/static/list/icon-play.svg"),
ICON_SITE: require('@/static/list/icon-site.svg'), ICON_SITE: require("@/static/list/icon-site.svg"),
ICON_ARROW_DOWN: require('@/static/list/icon-arrow-down.svg'), ICON_ARROW_DOWN: require("@/static/list/icon-arrow-down.svg"),
ICON_MENU_ITEM_SELECTED: require('@/static/list/icon-menu-item-selected.svg'), ICON_MENU_ITEM_SELECTED: require("@/static/list/icon-menu-item-selected.svg"),
ICON_ARROW_DOWN_WHITE: require('@/static/list/icon-arrow-down-white.svg'), ICON_ARROW_DOWN_WHITE: require("@/static/list/icon-arrow-down-white.svg"),
ICON_LIST_RIGHT_ARROW: require('@/static/list/icon-list-right-arrow.svg'), ICON_LIST_RIGHT_ARROW: require("@/static/list/icon-list-right-arrow.svg"),
ICON_ARROW_LEFT: require('@/static/detail/icon-arrow-left.svg'), ICON_ARROW_LEFT: require("@/static/detail/icon-arrow-left.svg"),
ICON_LOGO_GO: require('@/static/detail/icon-logo-go.svg'), ICON_LOGO_GO: require("@/static/detail/icon-logo-go.svg"),
ICON_MAP: require('@/static/publishBall/icon-map.svg'), ICON_MAP: require("@/static/publishBall/icon-map.svg"),
ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'), ICON_STADIUM: require("@/static/publishBall/icon-stadium.svg"),
ICON_ARRORW_SMALL: require('@/static/publishBall/icon-arrow-small.svg'), ICON_ARRORW_SMALL: require("@/static/publishBall/icon-arrow-small.svg"),
ICON_MAP_SEARCH: require('@/static/publishBall/icon-map-search.svg'), ICON_MAP_SEARCH: require("@/static/publishBall/icon-map-search.svg"),
ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'), ICON_HEART_CIRCLE: require("@/static/publishBall/icon-heartcircle.png"),
ICON_ADD: require('@/static/publishBall/icon-add.svg'), ICON_ADD: require("@/static/publishBall/icon-add.svg"),
ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'), ICON_COPY: require("@/static/publishBall/icon-arrow-right.svg"),
ICON_DELETE: require('@/static/publishBall/icon-delete.svg'), ICON_DELETE: require("@/static/publishBall/icon-delete.svg"),
ICON_RIGHT_MAX: require('@/static/publishBall/icon-right-max.svg'), ICON_RIGHT_MAX: require("@/static/publishBall/icon-right-max.svg"),
ICON_PLUS: require('@/static/publishBall/icon-plus.svg'), ICON_PLUS: require("@/static/publishBall/icon-plus.svg"),
ICON_GROUP: require('@/static/publishBall/icon-group.svg'), ICON_GROUP: require("@/static/publishBall/icon-group.svg"),
ICON_PERSON: require('@/static/publishBall/icon-person.svg'), ICON_PERSON: require("@/static/publishBall/icon-person.svg"),
ICON_PUBLISH: require('@/static/publishBall/icon-publish.png'), ICON_PUBLISH: require("@/static/publishBall/icon-publish.png"),
ICON_CIRCLE_UNSELECT: require('@/static/publishBall/icon-circle-unselect.svg'), ICON_CIRCLE_UNSELECT: require("@/static/publishBall/icon-circle-unselect.svg"),
ICON_CIRCLE_SELECT: require('@/static/publishBall/icon-circle-select-ring.svg'), ICON_CIRCLE_SELECT: require("@/static/publishBall/icon-circle-select-ring.svg"),
ICON_CIRCLE_SELECT_ARROW: require('@/static/publishBall/icon-circle-select-arrow.svg'), ICON_CIRCLE_SELECT_ARROW: require("@/static/publishBall/icon-circle-select-arrow.svg"),
ICON_LOGO: require('@/static/logo.svg'), ICON_LOGO: require("@/static/logo.svg"),
ICON_CHANGE: require('@/static/list/icon-change.svg'), ICON_CHANGE: require("@/static/list/icon-change.svg"),
ICON_DETAIL_MAP: require('@/static/detail/icon-map.svg'), ICON_DETAIL_MAP: require("@/static/detail/icon-map.svg"),
ICON_DETAIL_ARROW_RIGHT: require('@/static/detail/icon-arrow-right.svg'), ICON_DETAIL_ARROW_RIGHT: require("@/static/detail/icon-arrow-right.svg"),
ICON_DETAIL_NOTICE: require('@/static/detail/icon-notice.svg'), ICON_DETAIL_NOTICE: require("@/static/detail/icon-notice.svg"),
ICON_DETAIL_APPLICATION_ADD: require('@/static/detail/icon-application-add.svg'), ICON_DETAIL_APPLICATION_ADD: require("@/static/detail/icon-application-add.svg"),
ICON_DETAIL_COMMENT: require('@/static/detail/icon-comment.svg'), ICON_DETAIL_COMMENT: require("@/static/detail/icon-comment.svg"),
ICON_DETAIL_COMMENT_LIGHT: require('@/static/detail/icon-comment-light.svg'), ICON_DETAIL_COMMENT_LIGHT: require("@/static/detail/icon-comment-light.svg"),
ICON_DETAIL_SHARE: require('@/static/detail/icon-share-light.svg'), ICON_DETAIL_SHARE: require("@/static/detail/icon-share-light.svg"),
ICON_GUIDE_BAR_PUBLISH: require('@/static/common/guide-bar-publish.svg'), ICON_GUIDE_BAR_PUBLISH: require("@/static/common/guide-bar-publish.svg"),
ICON_NAVIGATOR_BACK: require('@/static/common/navigator-back.svg'), ICON_NAVIGATOR_BACK: require("@/static/common/navigator-back.svg"),
ICON_LIST_PLAYING_GAME: require('@/static/list/icon-paying-game.svg'), ICON_LIST_PLAYING_GAME: require("@/static/list/icon-paying-game.svg"),
ICON_LIST_LOAD_ERROR: require('@/static/list/icon-load-error.svg'), ICON_LIST_LOAD_ERROR: require("@/static/list/icon-load-error.svg"),
ICON_LIST_RELOAD: require('@/static/list/icon-reload.svg'), ICON_LIST_RELOAD: require("@/static/list/icon-reload.svg"),
ICON_LIST_EMPTY: require('@/static/emptyStatus/publish-empty.png'), ICON_LIST_EMPTY: require("@/static/emptyStatus/publish-empty.png"),
ICON_LIST_EMPTY_CARD: require('@/static/emptyStatus/publish-empty-card.png'), ICON_LIST_EMPTY_CARD: `${OSS_BASE}/front/ball/images/publish-empty-card.svg`,
ICON_LIST_SEARCH_SEARCH: require('@/static/search/icon-search.svg'), ICON_LIST_SEARCH_SEARCH: require("@/static/search/icon-search.svg"),
ICON_LIST_SEARCH_BACK: require('@/static/search/icon-back.svg'), ICON_LIST_SEARCH_BACK: require("@/static/search/icon-back.svg"),
ICON_LIST_SEARCH_CLEAR: require('@/static/search/icon-search-clear.svg'), ICON_LIST_SEARCH_CLEAR: require("@/static/search/icon-search-clear.svg"),
ICON_LIST_SEARCH_CLEAR_HISTORY: require('@/static/search/icon-clear-history.svg'), ICON_LIST_SEARCH_CLEAR_HISTORY: require("@/static/search/icon-clear-history.svg"),
ICON_LIST_SEARCH_SUGGESTION: require('@/static/search/icon-search-suggestion.svg'), ICON_LIST_SEARCH_SUGGESTION: require("@/static/search/icon-search-suggestion.svg"),
ICON_LIST_INPUT_LOGO: require('@/static/list/icon-input-logo.svg'), ICON_LIST_INPUT_LOGO: require("@/static/list/icon-input-logo.svg"),
ICON_IMPORTANT_BTN: require('@/static/publishBall/icon-important-btn.svg'), ICON_IMPORTANT_BTN: require("@/static/publishBall/icon-important-btn.svg"),
ICON_IMPORTANT_BLACK: require('@/static/publishBall/icon-important-black.svg'), ICON_IMPORTANT_BLACK: require("@/static/publishBall/icon-important-black.svg"),
ICON_ARROW_RIGHT_WHITE: require('@/static/publishBall/icon-arrow-right-white.svg'), ICON_ARROW_RIGHT_WHITE: require("@/static/publishBall/icon-arrow-right-white.svg"),
ICON_ARROW_RIGHT_BLACK: require('@/static/publishBall/icon-arrow-right-black.svg'), ICON_ARROW_RIGHT_BLACK: require("@/static/publishBall/icon-arrow-right-black.svg"),
ICON_EXAMINATION: require('@/static/userInfo/examination.svg'), ICON_EXAMINATION: require("@/static/userInfo/examination.svg"),
ICON_ARROW_GREEN: require('@/static/userInfo/arrow-green.svg'), ICON_ARROW_GREEN: require("@/static/userInfo/arrow-green.svg"),
ICON_COPY: require('@/static/publishBall/icon-copy.svg'), ICON_COPY: require("@/static/publishBall/icon-copy.svg"),
ICON_UPLOAD_IMG: require('@/static/publishBall/icon-upload-img.svg'), ICON_UPLOAD_IMG: require("@/static/publishBall/icon-upload-img.svg"),
ICON_UPLOAD_SUCCESS: require('@/static/publishBall/icon-upload-success.svg'), ICON_UPLOAD_SUCCESS: require("@/static/publishBall/icon-upload-success.svg"),
ICON_CLOSE: require('@/static/publishBall/icon-close.svg'), ICON_CLOSE: require("@/static/publishBall/icon-close.svg"),
ICON_LIST_NTPR: require('@/static/list/ntpr.svg'), ICON_LIST_NTPR: require("@/static/list/ntpr.svg"),
ICON_LIST_CHANGDA: require('@/static/list/icon-changda.svg'), ICON_LIST_CHANGDA: require("@/static/list/icon-changda.svg"),
ICON_LIST_CHANGDA_QIuju: require('@/static/list/changdaqiuju.png'), ICON_LIST_CHANGDA_QIuju: require("@/static/list/changdaqiuju.png"),
ICON_RELOCATE: require('@/static/list/icon-relocate.svg'), ICON_RELOCATE: require("@/static/list/icon-relocate.svg"),
} ICON_GAME_PLAY: require("@/static/list/icon_game_type.svg"),
};

View File

@@ -132,40 +132,39 @@ const ListContainer = (props) => {
); );
}; };
// 插入 banner 卡片 // showNumber 为 0 表示尚未同步,不参与截断;截断时只限制「数据条数」,插卡不占数据条数
const shouldLimitByShowNumber = showNumber > 0;
// 插入 banner 卡片(在 bannerListIndex 位置插入,不替换数据)
function insertBannerCard(list) { function insertBannerCard(list) {
if (!bannerListImage) return list; if (!bannerListImage) return list;
if (!list || !Array.isArray(list)) return list ?? []; if (!list || !Array.isArray(list)) {
list = [];
}
const idx = Number(bannerListIndex);
return [ return [
...list.slice(0, Number(bannerListIndex)), ...list.slice(0, idx),
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage }, { type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
...list.slice(Number(bannerListIndex)) ...list.slice(idx),
]; ];
} }
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面 // 对于没有 ntrp 等级的用户每个月展示一次插在第 2 条数据后面;插卡是插入不替换,保留全部 showNumber 条数据
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
function insertEvaluateCard(list) { function insertEvaluateCard(list) {
let result: any[]; if (!list || !Array.isArray(list)) return insertBannerCard(list ?? []);
if (!evaluateFlag) { const limitedList = shouldLimitByShowNumber ? list.slice(0, showNumber) : list;
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
} else if (!list || list.length === 0) { if (!evaluateFlag || hasTestInLastMonth) {
result = list; return insertBannerCard(limitedList);
} else if (hasTestInLastMonth) {
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
} else if (list.length <= 2) {
result = [...list, { type: "evaluateCard" }];
} else {
const [item1, item2, ...rest] = list;
result = [
item1,
item2,
{ type: "evaluateCard" },
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
];
} }
if (limitedList.length <= 2) {
return insertBannerCard([...limitedList, { type: "evaluateCard" }]);
}
const [item1, item2, ...rest] = limitedList;
const result = [item1, item2, { type: "evaluateCard" }, ...rest];
return insertBannerCard(result); return insertBannerCard(result);
} }
@@ -204,10 +203,12 @@ const ListContainer = (props) => {
); );
}; };
const showNoData = isShowNoData && !loading && memoizedList?.length === 0;
// 渲染列表 // 渲染列表
const renderList = () => { const renderList = () => {
// 请求数据为空 // 请求数据为空
if (isShowNoData) { if (showNoData) {
return ( return (
<ListLoadError <ListLoadError
reload={reload} reload={reload}

View File

@@ -25,9 +25,10 @@ dayjs.locale("zh-cn");
// 分享弹窗 // 分享弹窗
export default forwardRef(({ id, from, detail, userInfo }, ref) => { export default forwardRef(({ id, from, detail, userInfo }, ref) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [publishFlag, setPublishFlag] = useState(false);
const [shareImageUrl, setShareImageUrl] = useState(""); const [shareImageUrl, setShareImageUrl] = useState("");
const { fetchUserInfo } = useUserActions(); const { fetchUserInfo } = useUserActions();
const publishFlag = from === "publish";
// const posterRef = useRef(); // const posterRef = useRef();
const { max_participants, participant_count } = detail || {}; const { max_participants, participant_count } = detail || {};
@@ -57,18 +58,20 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
} }
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
show: async (publish_flag = false) => { show: async () => {
setPublishFlag(publish_flag);
if (publish_flag) {
try {
const url = await generateShareImageUrl();
setShareImageUrl(url);
} catch (e) {}
}
setVisible(true); setVisible(true);
}, },
})); }));
useEffect(() => {
if (from === "publish") {
generateShareImageUrl().then((url) => {
setShareImageUrl(url);
setVisible(true);
});
}
}, [from]);
async function generateShareImageUrl() { async function generateShareImageUrl() {
const { const {
play_type, play_type,
@@ -106,7 +109,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
// console.log(res, "res"); // console.log(res, "res");
return { return {
title: detail.title, title: detail.title,
imageUrl: url || "https://img.yzcdn.cn/vant/cat.jpeg", imageUrl: url,
path: `/game_pages/detail/index?id=${id}&from=share`, path: `/game_pages/detail/index?id=${id}&from=share`,
}; };
}); });
@@ -142,6 +145,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
const qrCodeUrl = qrCodeUrlRes.data.ossPath; const qrCodeUrl = qrCodeUrlRes.data.ossPath;
await delay(100); await delay(100);
// Taro.showLoading({ title: "生成中..." }); // Taro.showLoading({ title: "生成中..." });
console.log('url', qrCodeUrl)
const url = await generatePosterImage({ const url = await generatePosterImage({
playType: play_type, playType: play_type,
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`, ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
@@ -157,6 +161,8 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
time: `${startTime.format("ah")}${gameLength}`, time: `${startTime.format("ah")}${gameLength}`,
qrCodeUrl, qrCodeUrl,
}); });
console.log('urlend', url)
// Taro.hideLoading(); // Taro.hideLoading();
Taro.showShareImageMenu({ Taro.showShareImageMenu({
path: url, path: url,
@@ -183,7 +189,6 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
function onClose() { function onClose() {
setVisible(false); setVisible(false);
setPublishFlag(false);
} }
return ( return (

View File

@@ -54,12 +54,6 @@ function Index() {
await waitForAuthInit(); await waitForAuthInit();
// 然后再获取用户信息 // 然后再获取用户信息
await fetchUserInfo(); await fetchUserInfo();
await delay(1000);
if (from === "publish") {
handleShare(true);
}
}; };
init(); init();
}, []); }, []);
@@ -126,8 +120,12 @@ function Index() {
} }
} }
function handleShare(flag = false) { function handleShare() {
sharePopupRef.current.show(flag); if (!detail.id) {
toast("球局未加载完成,请稍后再试");
return false;
}
sharePopupRef.current.show();
} }
const handleJoinGame = async () => { const handleJoinGame = async () => {
@@ -293,6 +291,7 @@ function Index() {
currentUserInfo={myInfo} currentUserInfo={myInfo}
/> />
{/* share popup */} {/* share popup */}
{detail.id && myInfo.id && (
<SharePopup <SharePopup
ref={sharePopupRef} ref={sharePopupRef}
id={id as string} id={id as string}
@@ -300,6 +299,7 @@ function Index() {
detail={detail} detail={detail}
userInfo={myInfo} userInfo={myInfo}
/> />
)}
</View> </View>
</ScrollView> </ScrollView>
); );

View File

@@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { View, Text, Button, Image } from "@tarojs/components"; import { View, Text, Button, Image } from "@tarojs/components";
import Taro, { useRouter } from "@tarojs/taro"; import Taro, { useRouter } from "@tarojs/taro";
import { GeneralNavbar } from "@/components";
import { import {
wechat_auth_login, wechat_auth_login,
save_login_state, save_login_state,
@@ -171,6 +172,8 @@ const LoginPage: React.FC = () => {
<View className="bg_overlay"></View> <View className="bg_overlay"></View>
</View> </View>
<GeneralNavbar title="" showBack={true} showAvatar={false} onBack={handle_return_home} />
{/* 主要内容 */} {/* 主要内容 */}
<View className="login_main_content"> <View className="login_main_content">
{/* 品牌区域 */} {/* 品牌区域 */}
@@ -216,9 +219,9 @@ const LoginPage: React.FC = () => {
<Text className="button_text"></Text> <Text className="button_text"></Text>
</Button> </Button>
<View className="return_home_button link_button" onClick={handle_return_home}> {/* <View className="return_home_button link_button" onClick={handle_return_home}>
<Text className="button_text">返回首页</Text> <Text className="button_text">返回首页</Text>
</View> </View> */}
{/* 用户协议复选框 */} {/* 用户协议复选框 */}
<View className="terms_checkbox_section"> <View className="terms_checkbox_section">

View File

@@ -16,7 +16,9 @@ interface MyselfPageContentProps {
isActive?: boolean; isActive?: boolean;
} }
const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }) => { const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
isActive = true,
}) => {
const pickerOption = usePickerOption(); const pickerOption = usePickerOption();
const { statusNavbarHeightInfo } = useGlobalState() || {}; const { statusNavbarHeightInfo } = useGlobalState() || {};
const { totalHeight = 98 } = statusNavbarHeightInfo || {}; const { totalHeight = 98 } = statusNavbarHeightInfo || {};
@@ -65,20 +67,22 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
game_records: TennisMatch[] game_records: TennisMatch[]
): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => { ): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => {
const now = new Date().getTime(); const now = new Date().getTime();
return game_records.reduce(
(result, cur) => { // 使用for
let { end_time } = cur; const notEndGames: TennisMatch[] = [];
end_time = end_time.replace(/\s/, "T"); const finishedGames: TennisMatch[] = [];
new Date(end_time).getTime() > now for (const game of game_records) {
? result.notEndGames.push(cur) const { end_time } = game;
: result.finishedGames.push(cur); const end_time_str = end_time.replace(/\s/, "T");
return result; new Date(end_time_str).getTime() > now
}, ? notEndGames.push(game)
{ : finishedGames.push(game);
notEndGames: [] as TennisMatch[],
finishedGames: [] as TennisMatch[],
} }
);
console.log("notEndGames", notEndGames);
return { notEndGames, finishedGames };
}, },
[] []
); );
@@ -95,6 +99,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
} else { } else {
games_data = await UserService.get_participated_games(user_info.id); games_data = await UserService.get_participated_games(user_info.id);
} }
const sorted_games = games_data.sort((a, b) => { const sorted_games = games_data.sort((a, b) => {
return ( return (
new Date(a.original_start_time.replace(/\s/, "T")).getTime() - new Date(a.original_start_time.replace(/\s/, "T")).getTime() -
@@ -102,6 +108,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
); );
}); });
const { notEndGames, finishedGames } = classifyGameRecords(sorted_games); const { notEndGames, finishedGames } = classifyGameRecords(sorted_games);
console.log("notEndGames", notEndGames);
set_game_records(notEndGames); set_game_records(notEndGames);
setEndedGameRecords(finishedGames); setEndedGameRecords(finishedGames);
} catch (error) { } catch (error) {
@@ -250,16 +258,14 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
<View className={styles.gameTabsSection}> <View className={styles.gameTabsSection}>
<View className={styles.tabContainer}> <View className={styles.tabContainer}>
<View <View
className={`${styles.tabItem} ${ className={`${styles.tabItem} ${active_tab === "hosted" ? styles.active : ""
active_tab === "hosted" ? styles.active : ""
}`} }`}
onClick={() => setActiveTab("hosted")} onClick={() => setActiveTab("hosted")}
> >
<Text className={styles.tabText}></Text> <Text className={styles.tabText}></Text>
</View> </View>
<View <View
className={`${styles.tabItem} ${ className={`${styles.tabItem} ${active_tab === "participated" ? styles.active : ""
active_tab === "participated" ? styles.active : ""
}`} }`}
onClick={() => setActiveTab("participated")} onClick={() => setActiveTab("participated")}
> >
@@ -288,9 +294,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
overflow: "hidden", overflow: "hidden",
}} }}
listLoadErrorWrapperHeight="fit-content" listLoadErrorWrapperHeight="fit-content"
listLoadErrorWidth="320px" listLoadErrorWidth="410px"
listLoadErrorHeight="152px" listLoadErrorHeight="185px"
listLoadErrorScale="1.2"
defaultShowNum={3} defaultShowNum={3}
/> />
</ScrollView> </ScrollView>
@@ -312,9 +317,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
collapse={true} collapse={true}
style={{ paddingBottom: "90px", overflow: "hidden" }} style={{ paddingBottom: "90px", overflow: "hidden" }}
listLoadErrorWrapperHeight="fit-content" listLoadErrorWrapperHeight="fit-content"
listLoadErrorWidth="320px" listLoadErrorWidth="410px"
listLoadErrorHeight="152px" listLoadErrorHeight="185px"
listLoadErrorScale="1.2"
defaultShowNum={3} defaultShowNum={3}
/> />
</ScrollView> </ScrollView>

View File

@@ -1,6 +1,7 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '首页', navigationBarTitleText: '首页',
navigationStyle: 'custom', navigationStyle: 'custom',
navigationBarBackgroundColor: '#FAFAFA' navigationBarBackgroundColor: '#FAFAFA',
enableShareAppMessage: true,
}) })

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro";
import { OSS_BASE } from "@/config/api";
import { wechat_auth_login, save_login_state } from "@/services/loginService"; import { wechat_auth_login, save_login_state } from "@/services/loginService";
import { useUserActions } from "@/store/userStore"; import { useUserActions } from "@/store/userStore";
import { useGlobalState } from "@/store/global"; import { useGlobalState } from "@/store/global";
@@ -18,7 +19,11 @@ import { useDictionaryStore } from "@/store/dictionaryStore";
type TabType = "list" | "message" | "personal"; type TabType = "list" | "message" | "personal";
const MainPage: React.FC = () => { const MainPage: React.FC = () => {
const [currentTab, setCurrentTab] = useState<TabType>("list"); const { params } = useRouter();
const [currentTab, setCurrentTab] = useState<TabType>(() => {
const tab = params?.tab as TabType | undefined;
return tab === "list" || tab === "message" || tab === "personal" ? tab : "list";
});
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false); const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false); const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false); const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
@@ -35,6 +40,14 @@ const MainPage: React.FC = () => {
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } = const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
useGlobalState(); useGlobalState();
// 从分享链接进入时根据 ?tab= 定位到对应 tab
useEffect(() => {
const tab = params?.tab as TabType | undefined;
if (tab === "list" || tab === "message" || tab === "personal") {
setCurrentTab(tab);
}
}, [params?.tab]);
// 初始化:自动微信授权并获取用户信息 // 初始化:自动微信授权并获取用户信息
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
@@ -153,6 +166,21 @@ const MainPage: React.FC = () => {
[] []
); );
// 分享:首页、个人页均支持转发
// 分享图:配置 OSS 地址 + 路径(不含 ? 后参数),首页用 share_home.png个人页用 share_self.png
useShareAppMessage(() => {
const isList = currentTab === "list";
const isPersonal = currentTab === "personal";
const title = isList ? "约球 - 发现身边的球局" : isPersonal ? "约球 - 我的约球" : "约球";
const image_path = isPersonal ? "system/share_self.png" : "system/share_home.png";
const imageUrl = OSS_BASE ? `${OSS_BASE.replace(/\/$/, "")}/${image_path}` : "";
return {
title,
path: "/main_pages/index" + (isList ? "?tab=list" : isPersonal ? "?tab=personal" : ""),
imageUrl: imageUrl,
};
});
// 滚动到顶部 // 滚动到顶部
const scrollToTop = useCallback(() => { const scrollToTop = useCallback(() => {
// 如果当前是列表页,触发列表页内部滚动 // 如果当前是列表页,触发列表页内部滚动

View File

@@ -249,7 +249,7 @@ const CommentReply = () => {
<View className="comment-left"> <View className="comment-left">
<Image <Image
className="user-avatar" className="user-avatar"
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"} src={item.user_avatar }
mode="aspectFill" mode="aspectFill"
onClick={(e) => handleUserClick(e, item.user_id)} onClick={(e) => handleUserClick(e, item.user_id)}
/> />

View File

@@ -199,13 +199,6 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
} }
} }
// 当键盘显示时触发 changeTextarea
useEffect(() => {
if (isKeyboardVisible) {
changeTextarea(true)
}
}, [isKeyboardVisible])
const changePicker = (value:boolean) => { const changePicker = (value:boolean) => {
setOpenPicker(value); setOpenPicker(value);
} }
@@ -269,11 +262,13 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
<TextareaTag <TextareaTag
value={formData[item.prop]} value={formData[item.prop]}
onChange={(value) => { onChange={(value) => {
//changeTextarea(true)
updateFormData(item.prop, value) updateFormData(item.prop, value)
}} }}
// onBlur={() => changeTextarea(false)} // onBlur={() => {
onFocus={() => changeTextarea(true)} // }}
onFocus={() => {
changeTextarea(true)
}}
placeholder='有其他场地信息可备注' placeholder='有其他场地信息可备注'
options={(item.options || []).map((o) => ({ label: o, value: o }))} options={(item.options || []).map((o) => ({ label: o, value: o }))}
/> />

View File

@@ -169,7 +169,7 @@ class GameDetailService {
} }
async getLinkUrl(req: { path: string, query: string }): Promise<ApiResponse<{ url_link: string, path: string, query: string }>> { async getLinkUrl(req: { path: string, query: string }): Promise<ApiResponse<{ url_link: string, path: string, query: string }>> {
return httpService.post('/user/generate_url_link', req, { showLoading: true }) return httpService.post('/user/generate_url_link', req, { showLoading: false })
} }
} }

View File

@@ -151,6 +151,7 @@ interface BackendGameData {
longitude: string; longitude: string;
venue_type: string; venue_type: string;
surface_type: string; surface_type: string;
distance_km: string;
}; };
participants: { participants: {
user: { user: {
@@ -206,7 +207,7 @@ export class UserService {
latitude = parseFloat(game.venue_dtl.latitude) || latitude; latitude = parseFloat(game.venue_dtl.latitude) || latitude;
longitude = parseFloat(game.venue_dtl.longitude) || longitude; longitude = parseFloat(game.venue_dtl.longitude) || longitude;
} }
const distance = this.calculate_distance(latitude, longitude);
// 处理地点信息 - 优先使用venue_dtl中的信息 // 处理地点信息 - 优先使用venue_dtl中的信息
let location = game.location_name || game.location || "未知地点"; let location = game.location_name || game.location || "未知地点";
@@ -227,7 +228,7 @@ export class UserService {
original_start_time: game.start_time, original_start_time: game.start_time,
end_time: game.end_time || "", end_time: game.end_time || "",
location: location, location: location,
distance_km: parseFloat(distance.replace("km", "")) || 0, distance_km: game.venue_dtl?.distance_km ,
current_players: registered_count, current_players: registered_count,
max_players: max_count, max_players: max_count,
skill_level_min: parseInt(game.skill_level_min) || 0, skill_level_min: parseInt(game.skill_level_min) || 0,
@@ -303,20 +304,7 @@ export class UserService {
return `${date_str} ${time_str}`; return `${date_str} ${time_str}`;
} }
// 计算距离(模拟实现,实际需要根据用户位置计算)
private static calculate_distance(
latitude: number,
longitude: number
): string {
if (latitude === 0 && longitude === 0) {
return "未知距离";
}
// 这里应该根据用户当前位置计算实际距离
// 暂时返回模拟距离
const distances = ["1.2km", "2.5km", "3.8km", "5.1km", "7.3km"];
return distances[Math.floor(Math.random() * distances.length)];
}
// 获取用户信息 // 获取用户信息
static async get_user_info(user_id?: string): Promise<UserInfo> { static async get_user_info(user_id?: string): Promise<UserInfo> {
try { try {
@@ -359,8 +347,6 @@ export class UserService {
last_location_province: userData.last_location_province || "", last_location_province: userData.last_location_province || "",
last_location_city: userData.last_location_city || "", last_location_city: userData.last_location_city || "",
}; };
} else { } else {
throw new Error(response.message || "获取用户信息失败"); throw new Error(response.message || "获取用户信息失败");
} }
@@ -747,7 +733,7 @@ export const updateUserLocation = async (
const response = await httpService.post("/user/update_location", { const response = await httpService.post("/user/update_location", {
latitude, latitude,
longitude, longitude,
force force,
}); });
return response; return response;
} catch (error) { } catch (error) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,13 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3006_15051)">
<path d="M12.8375 3.17871C11.6157 2.03414 9.9731 1.33331 8.16683 1.33331C4.3929 1.33331 1.3335 4.39271 1.3335 8.16665C1.3335 11.9406 4.3929 15 8.16683 15C10.0384 15 11.7343 14.2475 12.9684 13.0287L8.00016 7.99998L12.8375 3.17871Z" stroke="black" stroke-width="1.33333" stroke-linejoin="round"/>
<path d="M13.3333 9.33335C14.0697 9.33335 14.6667 8.73639 14.6667 8.00002C14.6667 7.26365 14.0697 6.66669 13.3333 6.66669C12.597 6.66669 12 7.26365 12 8.00002C12 8.73639 12.597 9.33335 13.3333 9.33335Z" stroke="black" stroke-width="1.33333" stroke-linejoin="round"/>
<path d="M5.6665 4.33331V6.99998" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.3335 5.66669H7.00016" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_3006_15051">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -150,12 +150,13 @@ export const useKeyboardStore = create<KeyboardStore>((set, get) => ({
// 导出便捷的 hooks // 导出便捷的 hooks
export const useKeyboardHeight = () => { export const useKeyboardHeight = () => {
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardStore() const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardStore()
return { return {
keyboardHeight, keyboardHeight,
isKeyboardVisible, isKeyboardVisible,
addListener, addListener,
initializeKeyboardListener initializeKeyboardListener,
setKeyboardVisible
} }
} }

View File

@@ -5,7 +5,7 @@ import { UserService } from "@/services/userService";
export interface PickerOptionState { export interface PickerOptionState {
cities: any[]; cities: any[];
professions: any[]; professions: any[];
ntrpLevels: string[]; ntrpLevels: any[];
getCities: () => Promise<any>; getCities: () => Promise<any>;
getProfessions: () => Promise<any>; getProfessions: () => Promise<any>;
} }
@@ -13,7 +13,40 @@ export interface PickerOptionState {
export const usePickerOption = create<PickerOptionState>((set) => ({ export const usePickerOption = create<PickerOptionState>((set) => ({
cities: [], cities: [],
professions: [], professions: [],
ntrpLevels: ["1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "4.5+"], ntrpLevels: [
{
text: "1.5",
value: "1.5",
},
{
text: "2.0",
value: "2.0",
},
{
text: "2.5",
value: "2.5",
},
{
text: "3.0",
value: "3.0",
},
{
text: "3.5",
value: "3.5",
},
{
text: "4.0",
value: "4.0",
},
{
text: "4.5",
value: "4.5",
},
{
text: "4.5+",
value: "4.5+",
},
],
getCities: async () => { getCities: async () => {
try { try {
const res = await UserService.getCities(); const res = await UserService.getCities();

View File

@@ -8,7 +8,9 @@ import {
NicknameChangeStatus, NicknameChangeStatus,
updateNickname as updateNicknameApi, updateNickname as updateNicknameApi,
} from "@/services/userService"; } from "@/services/userService";
import evaluateService, { LastTimeTestResult } from "@/services/evaluateService"; import evaluateService, {
LastTimeTestResult,
} from "@/services/evaluateService";
import { useListStore } from "./listStore"; import { useListStore } from "./listStore";
export interface UserState { export interface UserState {
@@ -23,7 +25,6 @@ export interface UserState {
fetchLastTestResult: () => Promise<LastTimeTestResult | null>; fetchLastTestResult: () => Promise<LastTimeTestResult | null>;
} }
const getTimeNextDate = (time: string) => { const getTimeNextDate = (time: string) => {
const date = new Date(time); const date = new Date(time);
date.setDate(date.getDate() + 1); date.setDate(date.getDate() + 1);
@@ -51,8 +52,6 @@ export const useUser = create<UserState>()((set) => ({
const cachedCity = (Taro as any).getStorageSync?.(CITY_CACHE_KEY); const cachedCity = (Taro as any).getStorageSync?.(CITY_CACHE_KEY);
if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) { if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) {
// 如果有缓存的城市,使用缓存,不更新 area // 如果有缓存的城市,使用缓存,不更新 area
console.log("[userStore] 检测到缓存的城市,使用缓存,不更新 area"); console.log("[userStore] 检测到缓存的城市,使用缓存,不更新 area");
@@ -66,7 +65,10 @@ export const useUser = create<UserState>()((set) => ({
// 只有当 area 不存在时才使用用户信息中的位置 // 只有当 area 不存在时才使用用户信息中的位置
if (!currentArea) { if (!currentArea) {
const newArea: [string, string] = [userData.last_location_province||"", userData.last_location_city||""]; const newArea: [string, string] = [
userData.last_location_province || "",
userData.last_location_city || "",
];
listStore.updateArea(newArea); listStore.updateArea(newArea);
// 保存到缓存 // 保存到缓存
useUser.getState().updateCache(newArea); useUser.getState().updateCache(newArea);
@@ -102,8 +104,14 @@ export const useUser = create<UserState>()((set) => ({
const listStore = useListStore.getState(); const listStore = useListStore.getState();
const currentArea = listStore.area; const currentArea = listStore.area;
// 只有当 area 不存在或与 userLastLocationProvince 不一致时才更新 // 只有当 area 不存在或与 userLastLocationProvince 不一致时才更新
if (!currentArea || currentArea[1] !== userInfo.last_location_province) { if (
const newArea: [string, string] = [userInfo.last_location_province || "", userInfo.last_location_city || ""]; !currentArea ||
currentArea[1] !== userInfo.last_location_province
) {
const newArea: [string, string] = [
userInfo.last_location_province || "",
userInfo.last_location_city || "",
];
listStore.updateArea(newArea); listStore.updateArea(newArea);
} }
} }
@@ -127,7 +135,10 @@ export const useUser = create<UserState>()((set) => ({
// 如果已经有状态数据且不是强制更新,跳过,避免重复调用 // 如果已经有状态数据且不是强制更新,跳过,避免重复调用
if (!force) { if (!force) {
const currentState = useUser.getState(); const currentState = useUser.getState();
if (currentState.nicknameChangeStatus && Object.keys(currentState.nicknameChangeStatus).length > 0) { if (
currentState.nicknameChangeStatus &&
Object.keys(currentState.nicknameChangeStatus).length > 0
) {
return; return;
} }
} }

View File

@@ -44,6 +44,7 @@ const EditProfilePage: React.FC = () => {
country: info?.country ?? "", country: info?.country ?? "",
province: info?.province ?? "", province: info?.province ?? "",
city: info?.city ?? "", city: info?.city ?? "",
district: info?.district ?? "",
}; };
}; };
const [form_data, setFormData] = useState(getInitialFormData()); const [form_data, setFormData] = useState(getInitialFormData());
@@ -85,6 +86,7 @@ const EditProfilePage: React.FC = () => {
country: info?.country ?? "", country: info?.country ?? "",
province: info?.province ?? "", province: info?.province ?? "",
city: info?.city ?? "", city: info?.city ?? "",
district: info?.district ?? "",
}); });
} }
@@ -358,11 +360,11 @@ const EditProfilePage: React.FC = () => {
}); });
return; return;
} }
const [country, province, city] = e; const [province, city, district] = e;
handle_field_edit({ handle_field_edit({
country: String(country ?? ""),
province: String(province ?? ""), province: String(province ?? ""),
city: String(city ?? ""), city: String(city ?? ""),
district: String(district ?? ""),
}); });
}; };
@@ -660,15 +662,17 @@ const EditProfilePage: React.FC = () => {
<View className="item_right"> <View className="item_right">
<Text <Text
className={`item_value ${ className={`item_value ${
form_data.country ||
form_data.province || form_data.province ||
form_data.city form_data.city ||
form_data.district
? "" ? ""
: "placehoder" : "placehoder"
}`} }`}
> >
{form_data.country || form_data.province || form_data.city {form_data.province ||
? `${form_data.country} ${form_data.province} ${form_data.city}` form_data.city ||
form_data.district
? `${form_data.province} ${form_data.city} ${form_data.district}`
: "选择所在地区"} : "选择所在地区"}
</Text> </Text>
<Image <Image
@@ -885,8 +889,8 @@ const EditProfilePage: React.FC = () => {
visible={location_picker_visible} visible={location_picker_visible}
setvisible={setLocationPickerVisible} setvisible={setLocationPickerVisible}
value={ value={
form_data.country form_data.province
? [form_data.country, form_data.province, form_data.city] ? [form_data.province, form_data.city, form_data.district]
: getDefaultOption(cities) : getDefaultOption(cities)
} }
onChange={handle_location_change} onChange={handle_location_change}
@@ -899,15 +903,12 @@ const EditProfilePage: React.FC = () => {
title="选择 NTRP 自评水平" title="选择 NTRP 自评水平"
confirmText="保存" confirmText="保存"
ntrpTested={ntrpTested} ntrpTested={ntrpTested}
options={ntrpLevels.map((level) => ({ options={ntrpLevels}
text: level,
value: level,
}))}
type="ntrp" type="ntrp"
// img={(user_info as UserInfoType)?.avatar_url} // img={(user_info as UserInfoType)?.avatar_url}
visible={ntrp_picker_visible} visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible} setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level || "2.5"]} value={!form_data.ntrp_level ? ["2.5"] : [form_data.ntrp_level]}
onChange={handle_ntrp_level_change} onChange={handle_ntrp_level_change}
/> />
)} )}

View File

@@ -329,9 +329,8 @@ const OtherUserPage: React.FC = () => {
overflow: "hidden", overflow: "hidden",
}} }}
listLoadErrorWrapperHeight="fit-content" listLoadErrorWrapperHeight="fit-content"
listLoadErrorWidth="320px" listLoadErrorWidth="410px"
listLoadErrorHeight="152px" listLoadErrorHeight="185px"
listLoadErrorScale="1.2"
defaultShowNum={3} defaultShowNum={3}
/> />
</ScrollView> </ScrollView>
@@ -375,9 +374,8 @@ const OtherUserPage: React.FC = () => {
collapse={true} collapse={true}
style={{ paddingBottom: "90px", overflow: "hidden" }} style={{ paddingBottom: "90px", overflow: "hidden" }}
listLoadErrorWrapperHeight="fit-content" listLoadErrorWrapperHeight="fit-content"
listLoadErrorWidth="320px" listLoadErrorWidth="410px"
listLoadErrorHeight="152px" listLoadErrorHeight="185px"
listLoadErrorScale="1.2"
defaultShowNum={3} defaultShowNum={3}
/> />
</ScrollView> </ScrollView>

View File

@@ -35,21 +35,48 @@ export function base64ToTempFilePath(base64Data: string): Promise<string> {
} }
interface TaroGetImageInfo {
getImageInfo(option: {
src: string;
success?: (res: { width: number; height: number }) => void;
fail?: (err: unknown) => void;
}): void;
}
/** 获取图片宽高 */ /** 获取图片宽高 */
function getImageWh(src: string): Promise<{ width: number; height: number }> { function getImageWh(src: string): Promise<{ width: number; height: number }> {
return new Promise((resolve) => { return new Promise((resolve, reject) => {
Taro.getImageInfo({ (Taro as TaroGetImageInfo).getImageInfo({
src, src,
success: ({ width, height }) => resolve({ width, height }), success: ({ width, height }) => resolve({ width, height }),
fail: (e) => reject(e),
}); });
}); });
} }
/** 加载图片 */ /** 加载图片 */
function loadImage(canvas: any, src: string): Promise<any> { function loadImage(canvas: any, src: string): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve, reject) => {
let timer: any;
const img = canvas.createImage(); const img = canvas.createImage();
img.onload = () => resolve(img);
img.crossOrigin = "anonymous"
img.onload = () => {
clearTimeout(timer);
resolve(img);
};
img.onerror = () => {
clearTimeout(timer);
console.log('img error', src)
}
timer = setTimeout(() => {
reject(new Error(`Image load timeout: ${src}`));
}, 8000);
img.src = src; img.src = src;
}); });
} }
@@ -137,6 +164,7 @@ async function drawRotateCoverImage(
rotate = 0 // 旋转角度(弧度) rotate = 0 // 旋转角度(弧度)
) { ) {
const { width, height } = await getImageWh(src); const { width, height } = await getImageWh(src);
console.log('width', width, 'height', height)
const scale = Math.max(w / width, h / height); const scale = Math.max(w / width, h / height);
const newW = width * scale; const newW = width * scale;
const newH = height * scale; const newH = height * scale;
@@ -170,6 +198,7 @@ async function drawRotateCoverImage(
// 绘制 cover // 绘制 cover
ctx.drawImage(img, offsetX, offsetY, newW, newH); ctx.drawImage(img, offsetX, offsetY, newW, newH);
console.log('drawImage', offsetX, offsetY, newW, newH)
ctx.restore(); ctx.restore();
} }
@@ -281,6 +310,8 @@ function drawTextWrap(
/** 核心纯函数:生成海报图片 */ /** 核心纯函数:生成海报图片 */
export async function generatePosterImage(data: any): Promise<string> { export async function generatePosterImage(data: any): Promise<string> {
console.log("start !!!!"); console.log("start !!!!");
// const dpr = Taro.getWindowInfo().pixelRatio; // const dpr = Taro.getWindowInfo().pixelRatio;
const dpr = 1; const dpr = 1;
@@ -288,19 +319,30 @@ export async function generatePosterImage(data: any): Promise<string> {
const width = 600; const width = 600;
const height = 1000; const height = 1000;
console.log('width', width, 'height', height)
const canvas = Taro.createOffscreenCanvas({ type: "2d", width: width * dpr, height: height * dpr }); const canvas = Taro.createOffscreenCanvas({ type: "2d", width: width * dpr, height: height * dpr });
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
console.log('ctx', ctx)
// 背景渐变 // 背景渐变
roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC"); roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC");
console.log('bgUrl', bgUrl)
const bgImg = await loadImage(canvas, bgUrl); const bgImg = await loadImage(canvas, bgUrl);
ctx.drawImage(bgImg, 0, 0, width, height); ctx.drawImage(bgImg, 0, 0, width, height);
console.log('bgUrlend', )
roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6)); roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6));
// 顶部图片 // 顶部图片
const mainImg = await loadImage(canvas, data.mainCoursal); const mainImg = await loadImage(canvas, data.mainCoursal);
console.log('mainCoursal', data.mainCoursal)
await drawRotateCoverImage( await drawRotateCoverImage(
ctx, ctx,
canvas, canvas,
@@ -369,6 +411,8 @@ export async function generatePosterImage(data: any): Promise<string> {
left = 20; left = 20;
const dateImg = await loadImage(canvas, dateIcon); const dateImg = await loadImage(canvas, dateIcon);
console.log('dateIcon', dateIcon)
await drawCoverImage( await drawCoverImage(
ctx, ctx,
canvas, canvas,
@@ -395,6 +439,7 @@ export async function generatePosterImage(data: any): Promise<string> {
left = 20; left = 20;
top += 24; top += 24;
const mapImg = await loadImage(canvas, mapIcon); const mapImg = await loadImage(canvas, mapIcon);
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12); await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12);
@@ -431,11 +476,13 @@ export async function generatePosterImage(data: any): Promise<string> {
ctx.font = "400 20px sans-serif"; ctx.font = "400 20px sans-serif";
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */); ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
console.log('canvas', canvas)
// 导出图片 // 导出图片
const { tempFilePath } = await Taro.canvasToTempFilePath({ const { tempFilePath } = await Taro.canvasToTempFilePath({
canvas, canvas,
fileType: 'png', fileType: 'png',
quality: 0.7, quality: 0.7,
}); });
console.log('tempFilePath', tempFilePath)
return tempFilePath; return tempFilePath;
} }

View File

@@ -533,7 +533,6 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
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')
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') {