Compare commits
56 Commits
22965eedf3
...
fix/jgh/03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc8e84f5c | ||
| b84c3bb409 | |||
| fa41842e75 | |||
| 3bbb64d58c | |||
| 58cf46e93d | |||
|
|
8004b26bd1 | ||
|
|
98baa371ee | ||
|
|
f87859da0e | ||
| d3390d5e81 | |||
|
|
883ce3c2c4 | ||
|
|
63bcf6fe86 | ||
| a68da08c85 | |||
|
|
b854ef7505 | ||
|
|
2e4fd16383 | ||
|
|
636e218a63 | ||
|
|
47c19f0fa5 | ||
| 243bb59c1d | |||
| aed3c4cc54 | |||
| 05b89a4aeb | |||
|
|
1aa12a86c2 | ||
| 69248d33c8 | |||
| 1973ec3faa | |||
| abc2dfeecf | |||
| bafb44ff06 | |||
| 0e27d801a4 | |||
| 0a0203e36d | |||
| 2656c59475 | |||
| 23eb9dc467 | |||
| 44f971b1c2 | |||
| 4a553c63fc | |||
|
|
baa60bbfcb | ||
|
|
64f0267457 | ||
|
|
8688b6b82d | ||
|
|
1678f787a3 | ||
|
|
3571740280 | ||
|
|
b6801cdde2 | ||
|
|
e1ebcd949b | ||
|
|
044e84a5b4 | ||
|
|
7833c2f552 | ||
|
|
9e4282545f | ||
|
|
99c8026f61 | ||
| 2a9e8668a0 | |||
| 08092a89ab | |||
|
|
4f0cdad920 | ||
| 05966b2acb | |||
|
|
4cf2b959b5 | ||
|
|
43610dcf99 | ||
|
|
05aa820466 | ||
|
|
b154e31f8f | ||
|
|
669ee2fe4e | ||
|
|
281ee2b746 | ||
|
|
132c74d27c | ||
|
|
6b6a4c9480 | ||
|
|
0f8dd44f5a | ||
| 82ba753b8b | |||
| 159d81ed12 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,4 +8,6 @@ node_modules/
|
||||
src/config/env.ts
|
||||
.vscode
|
||||
*.http
|
||||
.cursor
|
||||
.codewiz
|
||||
|
||||
|
||||
@@ -473,7 +473,7 @@ async function safeMarkAsRead(type, ids) {
|
||||
})
|
||||
} catch (err) {
|
||||
// 标记已读失败不影响用户体验,静默处理
|
||||
console.error('标记已读失败:', err)
|
||||
console.warn('标记已读失败:', err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ function formatSize(bytes) {
|
||||
|
||||
function analyze() {
|
||||
if (!fs.existsSync(DIST_DIR)) {
|
||||
console.error('dist 目录不存在,请先执行 taro build --type weapp');
|
||||
console.warn('dist 目录不存在,请先执行 taro build --type weapp');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ export default {
|
||||
quiet: false,
|
||||
stats: true
|
||||
},
|
||||
mini: {},
|
||||
mini: {
|
||||
webpackChain(chain) {
|
||||
chain.devtool('source-map')
|
||||
}
|
||||
},
|
||||
h5: {},
|
||||
// 添加这个配置来显示完整错误信息
|
||||
compiler: {
|
||||
|
||||
@@ -11,7 +11,7 @@ const { envConfigs } = require(envConfigPath);
|
||||
|
||||
const config = envConfigs[appEnv];
|
||||
if (!config) {
|
||||
console.error(`[sync-project-config] Unknown APP_ENV: ${appEnv}`);
|
||||
console.warn(`[sync-project-config] Unknown APP_ENV: ${appEnv}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import dayjs from "dayjs";
|
||||
import classnames from "classnames";
|
||||
import CommentServices from "@/services/commentServices";
|
||||
import messageService from "@/services/messageService";
|
||||
import { delay } from "@/utils";
|
||||
import { delay, getBackendErrorMsg } from "@/utils";
|
||||
import type {
|
||||
BaseComment,
|
||||
Comment,
|
||||
@@ -342,7 +342,7 @@ export default forwardRef(function Comments(
|
||||
try {
|
||||
await messageService.markAsRead("comment", [message_id]);
|
||||
} catch (e) {
|
||||
console.error("标记评论已读失败:", e);
|
||||
console.warn("标记评论已读失败:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,36 +459,48 @@ export default forwardRef(function Comments(
|
||||
}
|
||||
|
||||
async function createComment(val: string) {
|
||||
const res = await CommentServices.createComment({ game_id, content: val });
|
||||
if (res.code === 0) {
|
||||
setComments((prev) => {
|
||||
commentCountUpdateRef.current?.(prev.length + 1);
|
||||
return [{ ...res.data, replies: [] }, ...prev];
|
||||
});
|
||||
toast("发布成功");
|
||||
try {
|
||||
const res = await CommentServices.createComment({ game_id, content: val });
|
||||
if (res.code === 0) {
|
||||
setComments((prev) => {
|
||||
commentCountUpdateRef.current?.(prev.length + 1);
|
||||
return [{ ...res.data, replies: [] }, ...prev];
|
||||
});
|
||||
toast("发布成功");
|
||||
} else {
|
||||
toast(getBackendErrorMsg(res, "评论失败"));
|
||||
}
|
||||
} catch (error) {
|
||||
toast(getBackendErrorMsg(error, "评论失败"));
|
||||
}
|
||||
}
|
||||
|
||||
async function replyComment({ parent_id, reply_to_user_id, content }) {
|
||||
const res = await CommentServices.replyComment({
|
||||
parent_id,
|
||||
reply_to_user_id,
|
||||
content,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
setComments((prev) => {
|
||||
return prev.map((item) => {
|
||||
if (item.id === parent_id) {
|
||||
return {
|
||||
...item,
|
||||
replies: [res.data, ...item.replies],
|
||||
reply_count: item.reply_count + 1,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
try {
|
||||
const res = await CommentServices.replyComment({
|
||||
parent_id,
|
||||
reply_to_user_id,
|
||||
content,
|
||||
});
|
||||
toast("回复成功");
|
||||
if (res.code === 0) {
|
||||
setComments((prev) => {
|
||||
return prev.map((item) => {
|
||||
if (item.id === parent_id) {
|
||||
return {
|
||||
...item,
|
||||
replies: [res.data, ...item.replies],
|
||||
reply_count: item.reply_count + 1,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
toast("回复成功");
|
||||
} else {
|
||||
toast(getBackendErrorMsg(res, "回复失败"));
|
||||
}
|
||||
} catch (error) {
|
||||
toast(getBackendErrorMsg(error, "回复失败"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
.common-popup {
|
||||
position: fixed;
|
||||
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) {
|
||||
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 {
|
||||
// padding: 12px 16px;
|
||||
|
||||
@@ -48,7 +48,14 @@ const CustomPopup: React.FC<CustomPopupProps> = ({
|
||||
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(() => {
|
||||
|
||||
@@ -134,7 +134,7 @@ const DistanceQuickFilterV2 = (props) => {
|
||||
throw new Error('获取位置信息失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('重新定位失败:', error);
|
||||
console.warn('重新定位失败:', error);
|
||||
(Taro as any).showToast({
|
||||
title: error?.message || '定位失败,请检查定位权限',
|
||||
icon: 'none',
|
||||
|
||||
@@ -32,36 +32,52 @@ const FilterPopup = (props: FilterPopupProps) => {
|
||||
const { timeBubbleData, gamesNum } = store;
|
||||
|
||||
/**
|
||||
* @description 处理字典选项
|
||||
* @param dictionaryValue 字典选项
|
||||
* @returns 选项列表
|
||||
* @description 日期排序
|
||||
* @param a 日期字符串
|
||||
* @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[]) => {
|
||||
let times: String[] = [];
|
||||
if (dates.length > 1) {
|
||||
times = [dayjs(dates[0]).format('YYYY-MM-DD'), dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')]
|
||||
onChange({
|
||||
'dateRange': times,
|
||||
})
|
||||
// ================================ 日期处理 ================================
|
||||
// 默认是是当前日期为开始日期,结束日期为当前日期 + 30天
|
||||
const defaultDateRange = [dayjs().format('YYYY-MM-DD'), dayjs().add(1, 'M').format('YYYY-MM-DD')];
|
||||
// 处理空数组的情况
|
||||
if (!dates.length) {
|
||||
onChange({ dateRange: defaultDateRange });
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(dates)) {
|
||||
|
||||
const currentDay = dayjs(dates[0]).format('YYYY-MM-DD');
|
||||
if (filterOptions.dateRange.length === 0 || filterOptions.dateRange.length === 2) {
|
||||
times.push(currentDay);
|
||||
} else {
|
||||
times = [...filterOptions.dateRange, currentDay].sort(
|
||||
(a, b) => new Date(a).getTime() - new Date(b).getTime()
|
||||
)
|
||||
}
|
||||
// 处理多日期范围选择(超过1个日期)
|
||||
if (dates.length > 1) {
|
||||
const dateRange = [
|
||||
dayjs(dates[0]).format('YYYY-MM-DD'),
|
||||
dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')
|
||||
];
|
||||
onChange({ dateRange });
|
||||
return;
|
||||
}
|
||||
|
||||
onChange({
|
||||
'dateRange': times,
|
||||
})
|
||||
// 处理单个日期选择
|
||||
const currentFilterOptionsDateRange = Array.isArray(filterOptions?.dateRange)
|
||||
? 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: []) => {
|
||||
|
||||
@@ -42,7 +42,7 @@ const FollowUserCard: React.FC<FollowUserCardProps> = ({ user, tabKey, onFollowC
|
||||
|
||||
onFollowChange?.(user.id, new_status);
|
||||
} catch (error) {
|
||||
console.error('关注操作失败:', error);
|
||||
console.warn('关注操作失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
@@ -67,7 +67,7 @@ const FollowUserCard: React.FC<FollowUserCardProps> = ({ user, tabKey, onFollowC
|
||||
onBlockSuccess?.(user.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除推荐人员失败:', error);
|
||||
console.warn('删除推荐人员失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
|
||||
@@ -16,8 +16,8 @@ const CancelPopup = forwardRef((props, ref) => {
|
||||
const { detail } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [cancelReason, setCancelReason] = useState("");
|
||||
const [inputFocus, setInputFocus] = useState(false);
|
||||
const onFinish = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const { current_players, participants = [], publisher_id } = detail;
|
||||
const realParticipants = participants
|
||||
@@ -32,16 +32,15 @@ const CancelPopup = forwardRef((props, ref) => {
|
||||
show: (onAct) => {
|
||||
onFinish.current = onAct;
|
||||
setVisible(true);
|
||||
// 使用 requestAnimationFrame 替代 setTimeout(0),性能更好
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
inputRef.current && inputRef.current.focus();
|
||||
});
|
||||
// 使用 Taro.nextTick 确保在下一个渲染周期后聚焦
|
||||
Taro.nextTick(() => {
|
||||
setInputFocus(true);
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
function onClose() {
|
||||
setInputFocus(false);
|
||||
setVisible(false);
|
||||
setCancelReason("");
|
||||
}
|
||||
@@ -85,13 +84,13 @@ const CancelPopup = forwardRef((props, ref) => {
|
||||
{hasOtherJoin && (
|
||||
<View className={styles.cancelReason}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
className={styles.input}
|
||||
placeholder="请输入取消理由"
|
||||
focus
|
||||
focus={inputFocus}
|
||||
value={cancelReason}
|
||||
onInput={(e) => setCancelReason(e.detail.value)}
|
||||
maxlength={100}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@@ -189,7 +188,8 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
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 || [])
|
||||
.filter((item) => item.status === "joined")
|
||||
@@ -207,7 +207,7 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
style={{ minHeight: "unset" }}
|
||||
>
|
||||
<View className={styles.container}>
|
||||
{!finished && !inTwoHours && !hasOtherParticiappants && (
|
||||
{!finished && !hasOtherParticiappants && beforeStart && (
|
||||
<View className={styles.button} onClick={handleEditGame}>
|
||||
编辑活动
|
||||
</View>
|
||||
@@ -217,12 +217,12 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
重新发布
|
||||
</View>
|
||||
)}
|
||||
{!finished && !inTwoHours && !hasOtherParticiappants && (
|
||||
{!finished && beforeStart && (
|
||||
<View className={styles.button} onClick={handleCancelGame}>
|
||||
取消活动
|
||||
</View>
|
||||
)}
|
||||
{!finished && hasJoin && (
|
||||
{!finished && beforeStart && hasJoin && (
|
||||
<View className={styles.button} onClick={handleQuitGame}>
|
||||
退出活动
|
||||
</View>
|
||||
|
||||
@@ -15,7 +15,7 @@ const GamePlayType = (props: IProps) => {
|
||||
const { name, onChange, value, options } = props;
|
||||
return (
|
||||
<View className={styles.gamePlayWrapper}>
|
||||
<TitleComponent title="玩法" icon={<Image src={img.ICON_SITE} />} />
|
||||
<TitleComponent title="玩法" icon={<Image src={img.ICON_GAME_PLAY} />} />
|
||||
<Bubble
|
||||
options={options}
|
||||
value={value}
|
||||
|
||||
@@ -146,7 +146,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
console.log(`[HomeNavbar] 距离上次选择"继续浏览"还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('[HomeNavbar] 检查定位弹窗显示条件失败:', error);
|
||||
console.warn('[HomeNavbar] 检查定位弹窗显示条件失败:', error);
|
||||
return true; // 出错时默认显示
|
||||
}
|
||||
};
|
||||
@@ -239,7 +239,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
// console.log(`距离上次选择"继续浏览"还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟`);
|
||||
// return false;
|
||||
// } catch (error) {
|
||||
// console.error('检查定位弹窗显示条件失败:', error);
|
||||
// console.warn('检查定位弹窗显示条件失败:', error);
|
||||
// return true; // 出错时默认显示
|
||||
// }
|
||||
// };
|
||||
@@ -276,7 +276,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
(Taro as any).setStorageSync(CITY_CHANGE_TIME_KEY, current_time);
|
||||
console.log(`[LocationDialog] 已记录用户切换城市的时间,2小时内不再提示`);
|
||||
} catch (error) {
|
||||
console.error('保存城市切换时间失败:', error);
|
||||
console.warn('保存城市切换时间失败:', error);
|
||||
}
|
||||
console.log("切换到用户详情中的位置信息并更新缓存:", detectedProvince);
|
||||
|
||||
@@ -304,7 +304,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
(Taro as any).setStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY, current_time);
|
||||
console.log(`[LocationDialog] 已记录用户选择"继续浏览"的时间,2小时内不再提示`);
|
||||
} catch (error) {
|
||||
console.error('保存定位弹窗关闭时间失败:', error);
|
||||
console.warn('保存定位弹窗关闭时间失败:', error);
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
@@ -409,7 +409,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
(Taro as any).setStorageSync(CITY_CHANGE_TIME_KEY, current_time);
|
||||
console.log("已保存城市到缓存并记录切换时间:", _newArea, current_time);
|
||||
} catch (error) {
|
||||
console.error("保存城市缓存失败:", error);
|
||||
console.warn("保存城市缓存失败:", error);
|
||||
}
|
||||
|
||||
// 先调用列表接口(会使用更新后的 state.area)
|
||||
|
||||
@@ -43,7 +43,7 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
|
||||
onChange([...images, ...newImages])
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择图片失败:', err)
|
||||
console.warn('选择图片失败:', err)
|
||||
}
|
||||
})
|
||||
}, [images.length, maxCount, onChange])
|
||||
|
||||
@@ -27,7 +27,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
||||
key,
|
||||
participants, // 参与者图片
|
||||
venue_image_list, // 场馆图片
|
||||
venue_description,
|
||||
location_name = '', // 场馆方
|
||||
game_type, // 球局类型
|
||||
}) => {
|
||||
// 参与者要前三个数据
|
||||
@@ -46,7 +46,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
||||
className="image"
|
||||
mode="aspectFill"
|
||||
lazyLoad
|
||||
defaultSource={`${OSS_BASE}/front/ball/images/publish-empty-card.svg`}
|
||||
defaultSource={`${OSS_BASE}/front/ball/images/publish-empty-card.png`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -257,13 +257,13 @@ const ListCard: React.FC<ListCardProps> = ({
|
||||
/>
|
||||
{/* <Text className="smoothTitle">{game_type}</Text> */}
|
||||
</View>
|
||||
{venue_description && <View className="line" />}
|
||||
{venue_description && (
|
||||
{location_name && <View className="line" />}
|
||||
{location_name && (
|
||||
<View className="localAreaContainer">
|
||||
<View className="localAreaTitle">场馆方:</View>
|
||||
<View className="localAreaWrapper">
|
||||
<Image className="localArea" src={venueImage} />
|
||||
<Text className="localAreaText">{venue_description}</Text>
|
||||
{venueImage && <Image className="localArea" src={venueImage} />}
|
||||
<Text className="localAreaText">{location_name}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
.listLoadErrorImg {
|
||||
width: 154px;
|
||||
height: 154px;
|
||||
}
|
||||
|
||||
.listLoadErrorText {
|
||||
|
||||
@@ -67,12 +67,6 @@ const NTRPEvaluatePopup = (props: NTRPEvaluatePopupProps, ref) => {
|
||||
const { updateUserInfo } = useUserActions();
|
||||
const userInfo = useUserInfo();
|
||||
const ntrpLevels = useNtrpLevels();
|
||||
const options = [
|
||||
ntrpLevels.map((item) => ({
|
||||
text: item,
|
||||
value: item,
|
||||
})),
|
||||
];
|
||||
const [evaCallback, setEvaCallback] = useState<EvaluateCallback>({
|
||||
type: "",
|
||||
next: () => {},
|
||||
@@ -171,7 +165,7 @@ const NTRPEvaluatePopup = (props: NTRPEvaluatePopupProps, ref) => {
|
||||
{visible && (
|
||||
<Picker
|
||||
visible
|
||||
options={options}
|
||||
options={ntrpLevels}
|
||||
defaultValue={[ntrp]}
|
||||
onChange={(val) => {
|
||||
console.log(val[0]);
|
||||
|
||||
@@ -52,7 +52,7 @@ const PopupPicker = ({
|
||||
ntrpTested,
|
||||
}: PickerProps) => {
|
||||
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
|
||||
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([]);
|
||||
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([...options]);
|
||||
const [pickerCurrentValue, setPickerCurrentValue] =
|
||||
useState<(string | number)[]>(value);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { View, Text, Image } from "@tarojs/components";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { useUserInfo } from "@/store/userStore";
|
||||
import {
|
||||
useEvaluate,
|
||||
EvaluateCallback,
|
||||
EvaluateScene,
|
||||
} from "@/store/evaluateStore";
|
||||
@@ -15,6 +14,7 @@ import styles from "./index.module.scss";
|
||||
import images from "@/config/images";
|
||||
import AiImportPopup from "@/publish_pages/publishBall/components/AiImportPopup";
|
||||
import NTRPEvaluatePopup from "../NTRPEvaluatePopup";
|
||||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||
|
||||
export interface PublishMenuProps {
|
||||
onPersonalPublish?: () => void;
|
||||
@@ -30,6 +30,7 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||
area
|
||||
} = useListState();
|
||||
|
||||
const supportedCitiesList = useDictionaryStore((s) => s.getDictionaryValue('supported_cities')) || [];
|
||||
|
||||
// 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调
|
||||
useEffect(() => {
|
||||
@@ -67,10 +68,10 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||
};
|
||||
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
||||
const [_, address] = area;
|
||||
if (address !== '上海市') {
|
||||
if (!supportedCitiesList.includes(address)) {
|
||||
(Taro as any).showModal({
|
||||
title: '提示',
|
||||
content: '仅上海地区开放,您可加入社群或切换城市',
|
||||
content: '该城市尚未开放,您可加入社群或切换城市',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
|
||||
@@ -194,17 +194,6 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
// 格式化 NTRP 显示
|
||||
function formatNtrpDisplay(level: string): string {
|
||||
if (!level) return "";
|
||||
// 检查是否包含 + 号
|
||||
const hasPlus = level.includes("+");
|
||||
const num = parseFloat(level);
|
||||
if (isNaN(num)) return level;
|
||||
const formatted = num % 1 === 0 ? num.toFixed(0) : num.toFixed(1);
|
||||
return hasPlus ? `${formatted}+` : formatted;
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
// 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制)
|
||||
generateImage: () => Promise.resolve(""),
|
||||
@@ -256,7 +245,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
const bgImg = await loadImage(canvas, shareBgUrl);
|
||||
ctx.drawImage(bgImg, 0, 0, width, height);
|
||||
} catch (error) {
|
||||
console.error("Failed to load background image:", error);
|
||||
console.warn("Failed to load background image:", error);
|
||||
// 如果加载失败,使用白色背景作为兜底
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
@@ -392,12 +381,12 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
);
|
||||
ctx.restore();
|
||||
} catch (error) {
|
||||
console.error("Failed to load docCopy image:", error);
|
||||
console.warn("Failed to load docCopy image:", error);
|
||||
}
|
||||
|
||||
currentY += (48 + 20) * scale; // 头像区域高度 + gap
|
||||
} catch (error) {
|
||||
console.error("Failed to load avatar image:", error);
|
||||
console.warn("Failed to load avatar image:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +410,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
ctx.textBaseline = "top";
|
||||
|
||||
const ntrpText = "NTRP";
|
||||
const levelText = formatNtrpDisplay(options.ntrpLevel);
|
||||
const levelText = options.ntrpLevel ?? "";
|
||||
const ntrpWidth = ctx.measureText(ntrpText).width;
|
||||
const levelWidth = ctx.measureText(levelText).width;
|
||||
const totalWidth = ntrpWidth + levelWidth; // 设计稿中紧挨着
|
||||
@@ -523,7 +512,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
iconSize,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to load icon:", error);
|
||||
console.warn("Failed to load icon:", error);
|
||||
}
|
||||
|
||||
// 绘制底部文字
|
||||
@@ -601,7 +590,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||
// 恢复上下文状态
|
||||
ctx.restore();
|
||||
} catch (error) {
|
||||
console.error("Failed to load QR code:", error);
|
||||
console.warn("Failed to load QR code:", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -575,7 +575,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
||||
setTempImagePath(res.tempFilePath)
|
||||
},
|
||||
fail: (error: any) => {
|
||||
console.error('图片生成失败:', error)
|
||||
console.warn('图片生成失败:', error)
|
||||
setIsDrawing(false)
|
||||
reject(error)
|
||||
}
|
||||
@@ -595,7 +595,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
||||
console.log('Canvas绘制命令已发送')
|
||||
|
||||
} catch (error) {
|
||||
console.error('绘制分享卡片失败:', error)
|
||||
console.warn('绘制分享卡片失败:', error)
|
||||
setIsDrawing(false) // 绘制失败,重置状态
|
||||
Taro.showToast({
|
||||
title: '生成分享卡片失败',
|
||||
|
||||
@@ -16,7 +16,7 @@ const SubscribeNotificationTip: React.FC<SubscribeNotificationTipProps> = ({
|
||||
navigateTo({
|
||||
url: '/other_pages/enable_notification/index',
|
||||
}).catch((err) => {
|
||||
console.error('跳转失败:', err);
|
||||
console.warn('跳转失败:', err);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -86,9 +86,9 @@ async function onChooseImageSuccess(tempFiles) {
|
||||
...fileRes,
|
||||
...(height > IMAGE_MAX_SIZE.height
|
||||
? {
|
||||
width: Math.floor(IMAGE_MAX_SIZE.height * image_aspect_ratio),
|
||||
height: IMAGE_MAX_SIZE.height,
|
||||
}
|
||||
width: Math.floor(IMAGE_MAX_SIZE.height * image_aspect_ratio),
|
||||
height: IMAGE_MAX_SIZE.height,
|
||||
}
|
||||
: { width: Math.floor(height * image_aspect_ratio), height }),
|
||||
};
|
||||
} else {
|
||||
@@ -96,9 +96,9 @@ async function onChooseImageSuccess(tempFiles) {
|
||||
...fileRes,
|
||||
...(width > IMAGE_MAX_SIZE.width
|
||||
? {
|
||||
width: IMAGE_MAX_SIZE.width,
|
||||
height: Math.floor(IMAGE_MAX_SIZE.width / image_aspect_ratio),
|
||||
}
|
||||
width: IMAGE_MAX_SIZE.width,
|
||||
height: Math.floor(IMAGE_MAX_SIZE.width / image_aspect_ratio),
|
||||
}
|
||||
: { width, height: Math.floor(width / image_aspect_ratio) }),
|
||||
};
|
||||
}
|
||||
@@ -119,7 +119,6 @@ export default function UploadFromWx(props: UploadFromWxProps) {
|
||||
sourceType: ["album", "camera"],
|
||||
}).then(async (res) => {
|
||||
const analyzedFiles = await onChooseImageSuccess(res.tempFiles);
|
||||
// cropping image to standard size
|
||||
const compressedTempFiles = await compressImage(analyzedFiles);
|
||||
|
||||
let start = Date.now();
|
||||
@@ -130,19 +129,22 @@ export default function UploadFromWx(props: UploadFromWxProps) {
|
||||
is_public: 1 as unknown as 0 | 1,
|
||||
id: (start++).toString(),
|
||||
}));
|
||||
const onFileUpdate = uploadApi.batchUpload(files).then((res) => {
|
||||
return res.map((item) => ({
|
||||
id: item.id,
|
||||
url: item ? item.data.file_url : "",
|
||||
}));
|
||||
});
|
||||
onAdd(
|
||||
files.map((item) => ({
|
||||
id: item.id,
|
||||
url: item.filePath,
|
||||
})),
|
||||
onFileUpdate
|
||||
);
|
||||
|
||||
Taro.showLoading({ title: "上传中..." });
|
||||
try {
|
||||
const uploadRes = await uploadApi.batchUpload(files);
|
||||
const successful = uploadRes
|
||||
.filter((item) => item.data != null)
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
url: (item.data as { file_url: string }).file_url,
|
||||
}));
|
||||
onAdd(successful, Promise.resolve(successful));
|
||||
} catch (e) {
|
||||
console.warn("批量上传失败:", e);
|
||||
} finally {
|
||||
Taro.hideLoading();
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
useUserActions,
|
||||
useNicknameChangeStatus,
|
||||
useLastTestResult,
|
||||
useUserInfo,
|
||||
} from "@/store/userStore";
|
||||
import { UserInfoType } from "@/services/userService";
|
||||
import {
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
useProfessions,
|
||||
useNtrpLevels,
|
||||
} from "@/store/pickerOptionsStore";
|
||||
import { formatNtrpDisplay } from "@/utils/helper";
|
||||
import { formatNtrpDisplay, getBackendErrorMsg } from "@/utils/helper";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
|
||||
// 用户信息接口
|
||||
@@ -73,7 +74,7 @@ const on_edit = () => {
|
||||
// 用户信息卡片组件
|
||||
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
editable = true,
|
||||
user_info,
|
||||
user_info: user_info_prop,
|
||||
is_current_user,
|
||||
is_following = false,
|
||||
collapseProfile,
|
||||
@@ -84,6 +85,9 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
set_user_info,
|
||||
onTab,
|
||||
}) => {
|
||||
const global_user_info = useUserInfo();
|
||||
// 查看别人页面时用传入的 user_info,个人页用全局 store
|
||||
const user_info = is_current_user ? global_user_info : (user_info_prop ?? global_user_info);
|
||||
const nickname_change_status = useNicknameChangeStatus();
|
||||
const { setShowGuideBar } = useGlobalState();
|
||||
const { updateUserInfo, updateNickname, fetchLastTestResult } =
|
||||
@@ -96,18 +100,16 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
const prevUserInfoRef = useRef<Partial<UserInfoType>>();
|
||||
|
||||
useEffect(() => {
|
||||
// 只在 user_info 真正变化时打印(通过 JSON 序列化比较)
|
||||
const prevStr = JSON.stringify(prevUserInfoRef.current);
|
||||
const currentStr = JSON.stringify(user_info);
|
||||
if (prevStr !== currentStr) {
|
||||
console.log("UserInfoCard 用户信息变化:", user_info);
|
||||
prevUserInfoRef.current = user_info;
|
||||
}
|
||||
// 如果全局状态中没有测试结果,则调用接口(使用请求锁,多个组件同时调用时只会请求一次)
|
||||
if (!lastTestResult && user_info?.id) {
|
||||
// 仅当前用户才拉取 NTRP 测试结果
|
||||
if (is_current_user && !lastTestResult && user_info?.id) {
|
||||
fetchLastTestResult();
|
||||
}
|
||||
}, [user_info?.id, lastTestResult, fetchLastTestResult]);
|
||||
}, [user_info?.id, lastTestResult, fetchLastTestResult, is_current_user]);
|
||||
|
||||
// 从全局状态中获取测试状态
|
||||
const ntrpTested = lastTestResult?.has_test_in_last_month || false;
|
||||
@@ -122,11 +124,15 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
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 });
|
||||
});
|
||||
}, [user_info])
|
||||
|
||||
useEffect(() => {
|
||||
const visibles = [
|
||||
@@ -244,10 +250,10 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("保存失败:", error);
|
||||
console.warn("保存失败:", error);
|
||||
Taro.showToast({
|
||||
title: "保存失败",
|
||||
icon: "error",
|
||||
title: getBackendErrorMsg(error, "保存失败"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -287,10 +293,10 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("保存失败:", error);
|
||||
console.warn("保存失败:", error);
|
||||
Taro.showToast({
|
||||
title: "保存失败",
|
||||
icon: "error",
|
||||
title: getBackendErrorMsg(error, "保存失败"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -372,7 +378,6 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
urls: [url],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="user_info_card">
|
||||
{/* 头像和基本信息 */}
|
||||
@@ -413,11 +418,11 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
<View className="stats_section">
|
||||
<View
|
||||
className="stats_container"
|
||||
// style={{
|
||||
// marginBottom: `${
|
||||
// collapseProfile && setMarginBottom ? "16px" : "unset"
|
||||
// }`,
|
||||
// }}
|
||||
// style={{
|
||||
// marginBottom: `${
|
||||
// collapseProfile && setMarginBottom ? "16px" : "unset"
|
||||
// }`,
|
||||
// }}
|
||||
>
|
||||
<View
|
||||
className="stat_item clickable"
|
||||
@@ -650,16 +655,16 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||
<PopupPicker
|
||||
showHeader={true}
|
||||
title="选择性别"
|
||||
options={[
|
||||
options={
|
||||
[
|
||||
{ text: "男", value: "0" },
|
||||
{ text: "女", value: "1" },
|
||||
{ text: "保密", value: "2" },
|
||||
],
|
||||
]}
|
||||
]
|
||||
}
|
||||
visible={gender_picker_visible}
|
||||
setvisible={setGenderPickerVisible}
|
||||
value={form_data.gender === "" ? ["0"] : [form_data.gender]}
|
||||
value={!form_data.gender ? ["0"] : [form_data.gender]}
|
||||
onChange={handle_gender_change}
|
||||
/>
|
||||
)}
|
||||
@@ -868,9 +873,8 @@ export const GameTabs: React.FC<GameTabsProps> = ({
|
||||
<Text className="tab_text">{hosted_text}</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`tab_item ${
|
||||
active_tab === "participated" ? "active" : ""
|
||||
}`}
|
||||
className={`tab_item ${active_tab === "participated" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => on_tab_change("participated")}
|
||||
>
|
||||
<Text className="tab_text">{participated_text}</Text>
|
||||
|
||||
@@ -13,7 +13,7 @@ import orderService from "@/services/orderService";
|
||||
import styles from "./index.module.scss";
|
||||
import closeIcon from "@/static/order/orderListClose.svg";
|
||||
|
||||
function genRefundNotice(refund_policy) {
|
||||
function genRefundNotice(refund_policy, order_amount) {
|
||||
if (refund_policy.length === 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -23,8 +23,7 @@ function genRefundNotice(refund_policy) {
|
||||
if (matchPolicyIndex === -1) {
|
||||
matchPolicyIndex = refund_policy.length - 1;
|
||||
}
|
||||
const { deadline_formatted, price, refund_rate } =
|
||||
refund_policy[matchPolicyIndex];
|
||||
const { time_range, price, refund_rate } = refund_policy[matchPolicyIndex];
|
||||
if (refund_rate === 1) {
|
||||
return {
|
||||
refundPrice: price,
|
||||
@@ -33,20 +32,18 @@ function genRefundNotice(refund_policy) {
|
||||
} else if (refund_rate === 0) {
|
||||
return {
|
||||
refundPrice: 0,
|
||||
notice: `当前退出不可退款,后续流程未明确,@麻真瑜`,
|
||||
notice: `当前退出不可退款,¥${order_amount} 将不予退回`,
|
||||
};
|
||||
}
|
||||
const refundPrice = Number(Math.ceil(price * refund_rate * 100) / 100);
|
||||
const leftHours = dayjs(deadline_formatted).diff(dayjs(), "hour");
|
||||
// const refundPrice = Number(Math.ceil(price * refund_rate * 100) / 100);
|
||||
// const leftHours = dayjs(deadline_formatted).diff(dayjs(), "hour");
|
||||
return {
|
||||
refundPrice,
|
||||
notice: `距活动开始已不足${leftHours}h,当前退出您需扣除${
|
||||
Math.floor((price - refundPrice) * 100) / 100
|
||||
}元`,
|
||||
refundPrice: price,
|
||||
notice: `活动开始${time_range},当前退出需扣除您${Math.ceil((order_amount - price) * 100) / 100}元`,
|
||||
};
|
||||
}
|
||||
|
||||
function renderCancelContent(refund_policy = []) {
|
||||
function renderCancelContent(refund_policy = [], amount) {
|
||||
const current = dayjs();
|
||||
const policyList = [
|
||||
{
|
||||
@@ -65,7 +62,7 @@ function renderCancelContent(refund_policy = []) {
|
||||
}),
|
||||
];
|
||||
const targetIndex = policyList.findIndex((item) => item.beforeCurrent);
|
||||
const { notice } = genRefundNotice(refund_policy);
|
||||
const { notice } = genRefundNotice(refund_policy, amount);
|
||||
return (
|
||||
<View className={styles.refundPolicy}>
|
||||
{/* <View className={styles.moduleTitle}>
|
||||
@@ -80,7 +77,7 @@ function renderCancelContent(refund_policy = []) {
|
||||
className={classnames(
|
||||
styles.policyItem,
|
||||
targetIndex > index && index !== 0 ? styles.pastItem : "",
|
||||
targetIndex === index ? styles.currentItem : ""
|
||||
targetIndex === index ? styles.currentItem : "",
|
||||
)}
|
||||
>
|
||||
<View className={styles.time}>
|
||||
@@ -169,7 +166,7 @@ export default forwardRef<RefundRef>(function RefundPopup(_props, ref) {
|
||||
onClick={onClose}
|
||||
/>
|
||||
</View>
|
||||
{renderCancelContent(refundPolicy)}
|
||||
{renderCancelContent(refundPolicy, orderData.amount)}
|
||||
<Button className={styles.action} onClick={handleConfirmQuit}>
|
||||
确认并退出
|
||||
</Button>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
ICON_LIST_LOAD_ERROR: require("@/static/list/icon-load-error.svg"),
|
||||
ICON_LIST_RELOAD: require("@/static/list/icon-reload.svg"),
|
||||
ICON_LIST_EMPTY: require("@/static/emptyStatus/publish-empty.png"),
|
||||
ICON_LIST_EMPTY_CARD: `${OSS_BASE}/front/ball/images/publish-empty-card.svg`,
|
||||
ICON_LIST_EMPTY_CARD: `${OSS_BASE}/front/ball/images/publish-empty-card.png`,
|
||||
ICON_LIST_SEARCH_SEARCH: require("@/static/search/icon-search.svg"),
|
||||
ICON_LIST_SEARCH_BACK: require("@/static/search/icon-back.svg"),
|
||||
ICON_LIST_SEARCH_CLEAR: require("@/static/search/icon-search-clear.svg"),
|
||||
@@ -72,4 +72,5 @@ export default {
|
||||
ICON_LIST_CHANGDA: require("@/static/list/icon-changda.svg"),
|
||||
ICON_LIST_CHANGDA_QIuju: require("@/static/list/changdaqiuju.png"),
|
||||
ICON_RELOCATE: require("@/static/list/icon-relocate.svg"),
|
||||
ICON_GAME_PLAY: require("@/static/list/icon_game_type.svg"),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,11 @@ import ListLoadError from "@/components/ListLoadError";
|
||||
import ListCardSkeleton from "@/components/ListCardSkeleton";
|
||||
import { useReachBottom } from "@tarojs/taro";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
||||
import {
|
||||
useUserInfo,
|
||||
useUserActions,
|
||||
useLastTestResult,
|
||||
} from "@/store/userStore";
|
||||
import { NTRPTestEntryCard } from "@/components";
|
||||
import { EvaluateScene } from "@/store/evaluateStore";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
@@ -46,7 +50,11 @@ const ListContainer = (props) => {
|
||||
const { fetchUserInfo, fetchLastTestResult } = useUserActions();
|
||||
// 使用全局状态中的测试结果,避免重复调用接口
|
||||
const lastTestResult = useLastTestResult();
|
||||
const { bannerListImage, bannerDetailImage, bannerListIndex = 0 } = useDictionaryStore((s) => s.bannerDict) || {};
|
||||
const {
|
||||
bannerListImage,
|
||||
bannerDetailImage,
|
||||
bannerListIndex = 0,
|
||||
} = useDictionaryStore((s) => s.bannerDict) || {};
|
||||
useReachBottom(() => {
|
||||
// 加载更多方法
|
||||
if (loading) {
|
||||
@@ -102,7 +110,7 @@ const ListContainer = (props) => {
|
||||
// 先等待静默登录完成
|
||||
await waitForAuthInit();
|
||||
// 然后再获取用户信息
|
||||
const userInfoId = userInfo && 'id' in userInfo ? userInfo.id : null;
|
||||
const userInfoId = userInfo && "id" in userInfo ? userInfo.id : null;
|
||||
if (!userInfoId) {
|
||||
await fetchUserInfo();
|
||||
return; // 等待下一次 useEffect 触发(此时 userInfo.id 已有值)
|
||||
@@ -113,7 +121,13 @@ const ListContainer = (props) => {
|
||||
}
|
||||
};
|
||||
init();
|
||||
}, [evaluateFlag, enableHomeCards, userInfo, lastTestResult, fetchLastTestResult]);
|
||||
}, [
|
||||
evaluateFlag,
|
||||
enableHomeCards,
|
||||
userInfo,
|
||||
lastTestResult,
|
||||
fetchLastTestResult,
|
||||
]);
|
||||
|
||||
// 从全局状态中获取测试状态
|
||||
const hasTestInLastMonth = lastTestResult?.has_test_in_last_month || false;
|
||||
@@ -132,46 +146,60 @@ const ListContainer = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
// 插入 banner 卡片
|
||||
// showNumber 为 0 表示尚未同步,不参与截断;截断时只限制「数据条数」,插卡不占数据条数
|
||||
const shouldLimitByShowNumber = showNumber > 0;
|
||||
|
||||
// 插入 banner 卡片(在 bannerListIndex 位置插入,不替换数据)
|
||||
function insertBannerCard(list) {
|
||||
if (!bannerListImage) return list;
|
||||
if (!list || !Array.isArray(list)) return list ?? [];
|
||||
if (!list || !Array.isArray(list)) {
|
||||
list = [];
|
||||
}
|
||||
const idx = Number(bannerListIndex);
|
||||
return [
|
||||
...list.slice(0, Number(bannerListIndex)),
|
||||
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
||||
...list.slice(Number(bannerListIndex))
|
||||
...list.slice(0, idx),
|
||||
{
|
||||
type: "banner",
|
||||
banner_image_url: bannerListImage,
|
||||
banner_detail_url: bannerDetailImage,
|
||||
},
|
||||
...list.slice(idx),
|
||||
];
|
||||
}
|
||||
|
||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
||||
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
|
||||
// 对于没有 ntrp 等级的用户每个月展示一次,插在第 2 条数据后面;插卡是插入不替换,保留全部 showNumber 条数据
|
||||
function insertEvaluateCard(list) {
|
||||
let result: any[];
|
||||
if (!list || !Array.isArray(list)) return insertBannerCard(list ?? []);
|
||||
|
||||
if (!evaluateFlag) {
|
||||
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||
} else if (!list || list.length === 0) {
|
||||
result = list;
|
||||
} 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),
|
||||
];
|
||||
const limitedList = shouldLimitByShowNumber
|
||||
? list.slice(0, showNumber)
|
||||
: list;
|
||||
|
||||
if (!evaluateFlag || hasTestInLastMonth) {
|
||||
return insertBannerCard(limitedList);
|
||||
}
|
||||
|
||||
if (limitedList.length <= 2) {
|
||||
return insertBannerCard([...limitedList, { type: "evaluateCard" }]);
|
||||
}
|
||||
|
||||
const [item1, item2, ...rest] = limitedList;
|
||||
const result = [item1, item2, { type: "evaluateCard" }, ...rest];
|
||||
return insertBannerCard(result);
|
||||
}
|
||||
|
||||
const memoizedList = useMemo(
|
||||
() => (enableHomeCards ? insertEvaluateCard(data) : data),
|
||||
[enableHomeCards, evaluateFlag, data, hasTestInLastMonth, showNumber, bannerListImage, bannerDetailImage, bannerListIndex]
|
||||
[
|
||||
enableHomeCards,
|
||||
evaluateFlag,
|
||||
data,
|
||||
hasTestInLastMonth,
|
||||
showNumber,
|
||||
bannerListImage,
|
||||
bannerDetailImage,
|
||||
bannerListIndex,
|
||||
]
|
||||
);
|
||||
|
||||
// 渲染 banner 卡片
|
||||
@@ -186,7 +214,9 @@ const ListContainer = (props) => {
|
||||
const target = item.banner_detail_url;
|
||||
if (target) {
|
||||
(Taro as any).navigateTo({
|
||||
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
||||
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(
|
||||
target
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -199,15 +229,16 @@ const ListContainer = (props) => {
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}
|
||||
>
|
||||
</View>
|
||||
></View>
|
||||
);
|
||||
};
|
||||
|
||||
const showNoData = isShowNoData && !loading && memoizedList?.length === 0;
|
||||
|
||||
// 渲染列表
|
||||
const renderList = () => {
|
||||
// 请求数据为空
|
||||
if (isShowNoData) {
|
||||
if (showNoData) {
|
||||
return (
|
||||
<ListLoadError
|
||||
reload={reload}
|
||||
@@ -232,7 +263,10 @@ const ListContainer = (props) => {
|
||||
}
|
||||
if (enableHomeCards && match?.type === "evaluateCard") {
|
||||
return (
|
||||
<NTRPTestEntryCard key={`evaluate-${index}`} type={EvaluateScene.list} />
|
||||
<NTRPTestEntryCard
|
||||
key={`evaluate-${index}`}
|
||||
type={EvaluateScene.list}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ListCard key={match?.id || index} {...match} />;
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import Taro from "@tarojs/taro";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import { calculateDistance } from "@/utils";
|
||||
import { calculateDistance, genGameLength } from "@/utils";
|
||||
import { View, Image, Text, Map } from "@tarojs/components";
|
||||
import img from "@/config/images";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
dayjs.locale("zh-cn");
|
||||
|
||||
function genGameLength(startTime: Dayjs, endTime: Dayjs) {
|
||||
if (!startTime || !endTime) {
|
||||
return "";
|
||||
}
|
||||
const hours = endTime.diff(startTime, "hour");
|
||||
if (Math.floor(hours / 24) >= 1) {
|
||||
const leftHours = Math.floor(hours % 24);
|
||||
return `${Math.floor(hours / 24)}天${
|
||||
leftHours !== 0 ? `${leftHours}小时` : ""
|
||||
}`;
|
||||
}
|
||||
return `${hours}小时`;
|
||||
}
|
||||
|
||||
function genGameRange(startTime: Dayjs, endTime: Dayjs) {
|
||||
if (!startTime || !endTime) {
|
||||
return "";
|
||||
|
||||
@@ -48,8 +48,8 @@ function genRecommendGames(games, location, avatar) {
|
||||
formatNtrpDisplay(skill_level_max) || "-"
|
||||
}`
|
||||
: skill_level_min === "1"
|
||||
? "无要求"
|
||||
: `${formatNtrpDisplay(skill_level_min)}以上`,
|
||||
? "无要求"
|
||||
: `${formatNtrpDisplay(skill_level_min)}以上`,
|
||||
playType: play_type,
|
||||
};
|
||||
});
|
||||
@@ -220,7 +220,9 @@ export default function OrganizerInfo(props) {
|
||||
>
|
||||
<Text>{game.venue}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.venueType}</Text>
|
||||
<Text style={{ whiteSpace: "nowrap" }}>
|
||||
{game.venueType}
|
||||
</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.distance}</Text>
|
||||
</View>
|
||||
@@ -247,7 +249,7 @@ export default function OrganizerInfo(props) {
|
||||
styles[
|
||||
"recommend-games-list-item-addon-message-applications"
|
||||
],
|
||||
styles.joinMsg
|
||||
styles.joinMsg,
|
||||
)}
|
||||
>
|
||||
<Text>已加入</Text>
|
||||
|
||||
@@ -489,7 +489,7 @@ export default function Participants(props) {
|
||||
<Text
|
||||
className={styles["participants-list-item-level"]}
|
||||
>
|
||||
{displayNtrp}
|
||||
NTRP {displayNtrp}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-role"]}>
|
||||
{role}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { forwardRef, useState, useEffect, useImperativeHandle } from "react";
|
||||
import { View, Button, Image, Text } from "@tarojs/components";
|
||||
import Taro, { useShareAppMessage } from "@tarojs/taro";
|
||||
import dayjs from "dayjs";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import classnames from "classnames";
|
||||
import { generateShareImage } from "@/utils";
|
||||
@@ -12,7 +12,7 @@ import WechatLogo from "@/static/detail/wechat_icon.svg";
|
||||
// import WechatTimeline from "@/static/detail/wechat_timeline.svg";
|
||||
import LinkIcon from "@/static/detail/link.svg";
|
||||
import CrossIcon from "@/static/detail/cross.svg";
|
||||
import { genNTRPRequirementText, navto } from "@/utils/helper";
|
||||
import { genNTRPRequirementText, navto, genGameLength } from "@/utils/helper";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
import { useUserActions } from "@/store/userStore";
|
||||
import { OSS_BASE } from "@/config/api";
|
||||
@@ -28,7 +28,19 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
const [shareImageUrl, setShareImageUrl] = useState("");
|
||||
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 posterRef = useRef();
|
||||
const { max_participants, participant_count } = detail || {};
|
||||
|
||||
@@ -50,6 +62,16 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
withShareTicket: false, // 是否需要返回 shareTicket
|
||||
isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版)
|
||||
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) {
|
||||
@@ -86,25 +108,34 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
const endTime = dayjs(end_time);
|
||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||
console.log(userInfo, "userInfo");
|
||||
const url = await generateShareImage({
|
||||
userAvatar: userInfo.avatar_url,
|
||||
userNickname: userInfo.nickname,
|
||||
gameType: play_type,
|
||||
skillLevel: `NTRP ${genNTRPRequirementText(
|
||||
skill_level_min,
|
||||
skill_level_max,
|
||||
)}`,
|
||||
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||
gameTime: `${startTime.format("ah")}点 ${gameLength}`,
|
||||
venueName: location_name,
|
||||
venueImages: image_list ? image_list : [],
|
||||
});
|
||||
return url;
|
||||
const currentUserInfo = await ensureUserInfo();
|
||||
try {
|
||||
const url = await generateShareImage({
|
||||
userAvatar: currentUserInfo.avatar_url,
|
||||
userNickname: currentUserInfo.nickname,
|
||||
gameType: play_type,
|
||||
skillLevel: `NTRP ${genNTRPRequirementText(
|
||||
skill_level_min,
|
||||
skill_level_max,
|
||||
)}`,
|
||||
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||
gameTime: `${startTime.format("ah")}点 ${gameLength}`,
|
||||
venueName: location_name,
|
||||
venueImages: image_list ? image_list : [],
|
||||
});
|
||||
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) => {
|
||||
await changeMessageType();
|
||||
await ensureUserInfo();
|
||||
const url = await generateShareImageUrl();
|
||||
// console.log(res, "res");
|
||||
return {
|
||||
@@ -128,38 +159,49 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
} = detail || {};
|
||||
// 先等待静默登录完成
|
||||
await waitForAuthInit();
|
||||
const userInfo = await fetchUserInfo();
|
||||
const { avatar_url, nickname } = userInfo;
|
||||
const currentUserInfo = await ensureUserInfo();
|
||||
const { avatar_url, nickname } = currentUserInfo;
|
||||
const startTime = dayjs(start_time);
|
||||
const endTime = dayjs(end_time);
|
||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||
// Taro.showLoading({ title: "生成中..." });
|
||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||
page: "game_pages/detail/index",
|
||||
scene: `id=${id}`,
|
||||
});
|
||||
// const qrCodeUrl = await base64ToTempFilePath(
|
||||
// qrCodeUrlRes.data.qr_code_base64
|
||||
// );
|
||||
const qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||
// const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||
const game_length = genGameLength(startTime, endTime);
|
||||
let qrCodeUrl = "";
|
||||
try {
|
||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||
page: "game_pages/detail/index",
|
||||
scene: `id=${id}`,
|
||||
});
|
||||
qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: "获取二维码失败", icon: "error" });
|
||||
return;
|
||||
}
|
||||
await delay(100);
|
||||
// Taro.showLoading({ title: "生成中..." });
|
||||
const url = await generatePosterImage({
|
||||
playType: play_type,
|
||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||
mainCoursal:
|
||||
image_list[0] && image_list[0].startsWith("http")
|
||||
? image_list[0]
|
||||
: `${OSS_BASE}/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
||||
nickname,
|
||||
avatarUrl: avatar_url,
|
||||
title,
|
||||
locationName: location_name,
|
||||
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
||||
qrCodeUrl,
|
||||
});
|
||||
console.log("url", qrCodeUrl);
|
||||
let url = "";
|
||||
try {
|
||||
url = await generatePosterImage({
|
||||
playType: play_type,
|
||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||
mainCoursal:
|
||||
image_list[0] && image_list[0].startsWith("http")
|
||||
? image_list[0]
|
||||
: `${OSS_BASE}/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
||||
nickname,
|
||||
avatarUrl: avatar_url,
|
||||
title,
|
||||
locationName: location_name,
|
||||
date: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||
time: `${startTime.format("ah")}点 ${game_length}`,
|
||||
qrCodeUrl,
|
||||
});
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: "生成海报失败,请重试", icon: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("urlend", url);
|
||||
// Taro.hideLoading();
|
||||
Taro.showShareImageMenu({
|
||||
path: url,
|
||||
|
||||
@@ -24,12 +24,12 @@ function isFull(counts) {
|
||||
} = counts;
|
||||
|
||||
if (
|
||||
max_players === current_players &&
|
||||
current_players >= max_players &&
|
||||
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
max_players === current_players &&
|
||||
current_players >= max_players &&
|
||||
is_substitute_supported === IsSubstituteSupported.SUPPORT
|
||||
) {
|
||||
return max_substitute_players === current_substitute_count;
|
||||
@@ -45,7 +45,7 @@ function RmbIcon() {
|
||||
function matchNtrpRequestment(
|
||||
target?: string,
|
||||
min?: string,
|
||||
max?: string
|
||||
max?: string,
|
||||
): boolean {
|
||||
// 目标值为空或 undefined
|
||||
if (!target?.trim()) return true;
|
||||
@@ -123,7 +123,7 @@ export default function StickyButton(props) {
|
||||
|
||||
Taro.navigateTo({
|
||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||
fullPath
|
||||
fullPath,
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export default function StickyButton(props) {
|
||||
const matchNtrpReq = matchNtrpRequestment(
|
||||
ntrp_level,
|
||||
skill_level_min,
|
||||
skill_level_max
|
||||
skill_level_max,
|
||||
);
|
||||
|
||||
const gameManageRef = useRef();
|
||||
@@ -173,7 +173,7 @@ export default function StickyButton(props) {
|
||||
}, [getCommentCount]);
|
||||
|
||||
function generateTextAndAction(
|
||||
user_action_status: null | { [key: string]: boolean }
|
||||
user_action_status: null | { [key: string]: boolean },
|
||||
):
|
||||
| undefined
|
||||
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
||||
@@ -271,7 +271,7 @@ export default function StickyButton(props) {
|
||||
const res = await OrderService.getUnpaidOrder(id);
|
||||
if (res.code === 0) {
|
||||
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
|
||||
className={classnames(
|
||||
styles["detail-main-action"],
|
||||
available ? "" : styles.disabled
|
||||
available ? "" : styles.disabled,
|
||||
)}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -16,6 +16,11 @@ export default function VenueInfo(props) {
|
||||
venue_image_list = [],
|
||||
} = detail;
|
||||
|
||||
// 统一为 URL 数组:接口可能是 { id, url }[] 或 string[]
|
||||
const screenshot_urls = (venue_image_list || []).map((item) =>
|
||||
typeof item === "string" ? item : (item?.url ?? "")
|
||||
).filter(Boolean);
|
||||
|
||||
function showScreenShot() {
|
||||
setVisible(true);
|
||||
}
|
||||
@@ -23,10 +28,10 @@ export default function VenueInfo(props) {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
function previewImage(current_url) {
|
||||
function previewImage(current_url: string) {
|
||||
Taro.previewImage({
|
||||
current: current_url,
|
||||
urls: venue_image_list || [],
|
||||
urls: screenshot_urls,
|
||||
});
|
||||
}
|
||||
return (
|
||||
@@ -34,14 +39,14 @@ export default function VenueInfo(props) {
|
||||
{/* venue detail title and venue ordered status */}
|
||||
<View className={styles["venue-detail-title"]}>
|
||||
<Text>场馆详情</Text>
|
||||
{venue_image_list?.length > 0 ? (
|
||||
{screenshot_urls.length > 0 ? (
|
||||
<>
|
||||
<Text>·</Text>
|
||||
<View
|
||||
className={styles["venue-reserve-status"]}
|
||||
onClick={showScreenShot}
|
||||
>
|
||||
<Text>已订场</Text>
|
||||
<Text>查看订场截图</Text>
|
||||
<Image
|
||||
className={styles["venue-reserve-screenshot"]}
|
||||
src={img.ICON_DETAIL_ARROW_RIGHT}
|
||||
@@ -81,22 +86,20 @@ export default function VenueInfo(props) {
|
||||
<View className={styles["venue-screenshot-title"]}>预定截图</View>
|
||||
<ScrollView scrollY className={styles["venue-screenshot-scroll-view"]}>
|
||||
<View className={styles["venue-screenshot-image-list"]}>
|
||||
{venue_image_list?.length > 0 &&
|
||||
venue_image_list.map((url, index) => {
|
||||
return (
|
||||
<View
|
||||
className={styles["venue-screenshot-image-item"]}
|
||||
onClick={previewImage.bind(null, url)}
|
||||
key={index}
|
||||
>
|
||||
<Image
|
||||
className={styles["venue-screenshot-image-item-image"]}
|
||||
mode="aspectFill"
|
||||
src={url}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
{screenshot_urls.length > 0 &&
|
||||
screenshot_urls.map((url, index) => (
|
||||
<View
|
||||
className={styles["venue-screenshot-image-item"]}
|
||||
onClick={() => previewImage(url)}
|
||||
key={index}
|
||||
>
|
||||
<Image
|
||||
className={styles["venue-screenshot-image-item-image"]}
|
||||
mode="aspectFill"
|
||||
src={url}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</CommonPopup>
|
||||
|
||||
@@ -86,7 +86,7 @@ function Index() {
|
||||
// handleShare(true);
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error("用户位置更新失败", error);
|
||||
console.warn("用户位置更新失败", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ const HomePage: React.FC = () => {
|
||||
if (loginResult.success) {
|
||||
// 静默登录成功,获取用户信息
|
||||
fetchUserInfo().catch((error) => {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
});
|
||||
checkNicknameChangeStatus().catch((error) => {
|
||||
console.error("检查昵称变更状态失败:", error);
|
||||
console.warn("检查昵称变更状态失败:", error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("静默登录失败:", error);
|
||||
console.warn("静默登录失败:", error);
|
||||
// 静默登录失败不影响使用
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ const VerificationPage: React.FC = () => {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("发送验证码异常:", error);
|
||||
console.warn("发送验证码异常:", error);
|
||||
Taro.showToast({
|
||||
title: "发送失败,请重试",
|
||||
icon: "none",
|
||||
|
||||
@@ -67,6 +67,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
} = store;
|
||||
|
||||
const supportedCitiesList = useDictionaryStore((s) => s.getDictionaryValue('supported_cities', ['上海市'])) || [];
|
||||
// 首页是否展示二维码,由 getDictionaryManyKey 的 show_home_qrcode 控制,默认 true 保持原样
|
||||
const showHomeQrcode = useDictionaryStore((s) => s.getDictionaryValue('show_home_qrcode', true));
|
||||
|
||||
const {
|
||||
isShowFilterPopup,
|
||||
@@ -227,18 +229,16 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
// 分批异步执行初始化操作,避免阻塞首屏渲染
|
||||
// 1. 立即执行:获取城市、二维码和行政区列表(轻量操作)
|
||||
getCities();
|
||||
getCityQrCode();
|
||||
getDistricts(); // 新增:获取行政区列表
|
||||
if (showHomeQrcode) getCityQrCode();
|
||||
getDistricts();
|
||||
|
||||
// 只有当页面激活时才加载位置和列表数据
|
||||
if (isActive) {
|
||||
getLocation().catch((error) => {
|
||||
console.error('获取位置信息失败:', error);
|
||||
console.warn('获取位置信息失败:', error);
|
||||
});
|
||||
}
|
||||
}, [isActive]);
|
||||
}, [isActive, showHomeQrcode]);
|
||||
|
||||
// 记录上一次的城市,用于检测城市变化
|
||||
const prevAreaRef = useRef<[string, string] | null>(null);
|
||||
@@ -309,7 +309,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
lastLoadedAreaRef.current = [...currentArea] as [string, string];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("重新加载数据失败:", error);
|
||||
console.warn("重新加载数据失败:", error);
|
||||
}
|
||||
}, delayMs);
|
||||
prevIsActiveRef.current = isActive;
|
||||
@@ -375,7 +375,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
await updateUserLocation(location.latitude, location.longitude, isFirstCall);
|
||||
hasUpdatedLocationRef.current = true;
|
||||
} catch (error) {
|
||||
console.error("更新用户位置失败:", error);
|
||||
console.warn("更新用户位置失败:", error);
|
||||
}
|
||||
}
|
||||
// 先调用列表接口
|
||||
@@ -462,7 +462,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
await getMatchesData();
|
||||
await fetchGetGamesCount();
|
||||
} catch (error) {
|
||||
console.error("刷新列表失败:", error);
|
||||
console.warn("刷新列表失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -477,7 +477,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
const { fetchDictionary } = useDictionaryStore.getState();
|
||||
await fetchDictionary();
|
||||
} catch (error) {
|
||||
console.error("初始化字典数据失败:", error);
|
||||
console.warn("初始化字典数据失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -537,7 +537,13 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
return (
|
||||
<>
|
||||
{shouldShowNoGames ? (
|
||||
renderCityQrcode()
|
||||
showHomeQrcode ? (
|
||||
renderCityQrcode()
|
||||
) : (
|
||||
<View className={styles.cqContainer}>
|
||||
<Text>当前城市暂无球局,敬请期待</Text>
|
||||
</View>
|
||||
)
|
||||
) : (
|
||||
<View ref={scrollContextRef}>
|
||||
<View className={styles.listPage} style={{ paddingTop: totalHeight }}>
|
||||
@@ -610,7 +616,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
try {
|
||||
await loadMoreMatches();
|
||||
} catch (error) {
|
||||
console.error("加载更多失败:", error);
|
||||
console.warn("加载更多失败:", error);
|
||||
} finally {
|
||||
loadingMoreRef.current = false;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { EvaluateScene } from "@/store/evaluateStore";
|
||||
import { useUserInfo, useUserActions } from "@/store/userStore";
|
||||
import { usePickerOption } from "@/store/pickerOptionsStore";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import { useListState } from "@/store/listStore";
|
||||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||
|
||||
interface MyselfPageContentProps {
|
||||
isActive?: boolean;
|
||||
@@ -41,6 +43,10 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
const [collapseProfile, setCollapseProfile] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const { area } = useListState();
|
||||
const supportedCitiesList =
|
||||
useDictionaryStore((s) => s.getDictionaryValue("supported_cities")) || [];
|
||||
|
||||
useEffect(() => {
|
||||
pickerOption.getCities();
|
||||
pickerOption.getProfessions();
|
||||
@@ -76,13 +82,12 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
const end_time_str = end_time.replace(/\s/, "T");
|
||||
new Date(end_time_str).getTime() > now
|
||||
? notEndGames.push(game)
|
||||
: finishedGames.push(game);
|
||||
: finishedGames.unshift(game);
|
||||
}
|
||||
|
||||
console.log("notEndGames", notEndGames);
|
||||
|
||||
return { notEndGames, finishedGames };
|
||||
|
||||
},
|
||||
[]
|
||||
);
|
||||
@@ -100,7 +105,6 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
games_data = await UserService.get_participated_games(user_info.id);
|
||||
}
|
||||
|
||||
|
||||
const sorted_games = games_data.sort((a, b) => {
|
||||
return (
|
||||
new Date(a.original_start_time.replace(/\s/, "T")).getTime() -
|
||||
@@ -113,7 +117,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
set_game_records(notEndGames);
|
||||
setEndedGameRecords(finishedGames);
|
||||
} catch (error) {
|
||||
console.error("加载球局数据失败:", error);
|
||||
console.warn("加载球局数据失败:", error);
|
||||
}
|
||||
}, [active_tab, user_info, classifyGameRecords]);
|
||||
|
||||
@@ -146,7 +150,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
duration: 1500,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("关注操作失败:", error);
|
||||
console.warn("关注操作失败:", error);
|
||||
(Taro as any).showToast({
|
||||
title: "操作失败,请重试",
|
||||
icon: "error",
|
||||
@@ -156,6 +160,16 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
};
|
||||
|
||||
const goPublish = () => {
|
||||
const [_, address] = area;
|
||||
if (!supportedCitiesList.includes(address)) {
|
||||
(Taro as any).showModal({
|
||||
title: "提示",
|
||||
content: "该城市尚未开放,您可加入社群或切换城市",
|
||||
showCancel: false,
|
||||
confirmText: "知道了",
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Taro as any).navigateTo({
|
||||
url: "/publish_pages/publishBall/index",
|
||||
});
|
||||
@@ -184,7 +198,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
try {
|
||||
await Promise.all([fetchUserInfo(), load_game_data()]);
|
||||
} catch (error) {
|
||||
console.error("刷新失败:", error);
|
||||
console.warn("刷新失败:", error);
|
||||
(Taro as any).showToast({
|
||||
title: "刷新失败,请重试",
|
||||
icon: "none",
|
||||
@@ -258,15 +272,17 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
<View className={styles.gameTabsSection}>
|
||||
<View className={styles.tabContainer}>
|
||||
<View
|
||||
className={`${styles.tabItem} ${active_tab === "hosted" ? styles.active : ""
|
||||
}`}
|
||||
className={`${styles.tabItem} ${
|
||||
active_tab === "hosted" ? styles.active : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("hosted")}
|
||||
>
|
||||
<Text className={styles.tabText}>我主办的</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`${styles.tabItem} ${active_tab === "participated" ? styles.active : ""
|
||||
}`}
|
||||
className={`${styles.tabItem} ${
|
||||
active_tab === "participated" ? styles.active : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("participated")}
|
||||
>
|
||||
<Text className={styles.tabText}>我参与的</Text>
|
||||
@@ -287,7 +303,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
btnImg="ICON_ADD"
|
||||
reload={goPublish}
|
||||
isShowNoData={game_records.length === 0}
|
||||
loadMoreMatches={() => { }}
|
||||
loadMoreMatches={() => {}}
|
||||
collapse={true}
|
||||
style={{
|
||||
paddingBottom: ended_game_records.length ? 0 : "90px",
|
||||
@@ -313,7 +329,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||
error={null}
|
||||
errorImg="ICON_LIST_EMPTY_CARD"
|
||||
isShowNoData={ended_game_records.length === 0}
|
||||
loadMoreMatches={() => { }}
|
||||
loadMoreMatches={() => {}}
|
||||
collapse={true}
|
||||
style={{ paddingBottom: "90px", overflow: "hidden" }}
|
||||
listLoadErrorWrapperHeight="fit-content"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '首页',
|
||||
navigationStyle: 'custom',
|
||||
navigationBarBackgroundColor: '#FAFAFA'
|
||||
})
|
||||
navigationBarTitleText: '首页',
|
||||
navigationStyle: 'custom',
|
||||
navigationBarBackgroundColor: '#FAFAFA',
|
||||
enableShareAppMessage: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
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 { useUserActions } from "@/store/userStore";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
@@ -18,7 +19,11 @@ import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||
type TabType = "list" | "message" | "personal";
|
||||
|
||||
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 [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
|
||||
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
|
||||
@@ -35,6 +40,14 @@ const MainPage: React.FC = () => {
|
||||
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
||||
useGlobalState();
|
||||
|
||||
// 从分享链接进入时根据 ?tab= 定位到对应 tab
|
||||
useEffect(() => {
|
||||
const tab = params?.tab as TabType | undefined;
|
||||
if (tab === "list" || tab === "message" || tab === "personal") {
|
||||
setCurrentTab(tab);
|
||||
}
|
||||
}, [params?.tab]);
|
||||
|
||||
// 初始化:自动微信授权并获取用户信息
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
@@ -55,7 +68,7 @@ const MainPage: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("微信授权异常:", error);
|
||||
console.warn("微信授权异常:", error);
|
||||
setAuthErrorMessage("微信授权失败,请重试");
|
||||
setShowAuthError(true);
|
||||
return;
|
||||
@@ -68,7 +81,7 @@ const MainPage: React.FC = () => {
|
||||
await fetchUserInfo();
|
||||
await checkNicknameChangeStatus();
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -153,6 +166,43 @@ const MainPage: React.FC = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
// 分享:按 tab 用 map 对应文案与分享图
|
||||
const share_config: Record<
|
||||
TabType,
|
||||
{ title: string; image_path: string; query: string }
|
||||
> = {
|
||||
list: {
|
||||
title: "有你就有场,发现身边好球友和好球局",
|
||||
image_path: "system/share_home.png",
|
||||
query: "?tab=list",
|
||||
},
|
||||
personal: {
|
||||
title: "快来有场,约我一起打网球~",
|
||||
image_path: "system/share_self.png",
|
||||
query: "?tab=personal",
|
||||
},
|
||||
message: {
|
||||
title: "查看球友动态",
|
||||
image_path: "system/share_home.png",
|
||||
query: "?tab=message",
|
||||
},
|
||||
};
|
||||
useShareAppMessage(() => {
|
||||
const config = share_config[currentTab] ?? {
|
||||
title: "约球",
|
||||
image_path: "system/share_home.png",
|
||||
query: "",
|
||||
};
|
||||
// const imageUrl = OSS_BASE
|
||||
// ? `${OSS_BASE.replace(/\/$/, "")}/${config.image_path}`
|
||||
// : "";
|
||||
return {
|
||||
title: config.title,
|
||||
path: "/main_pages/index" + config.query,
|
||||
// imageUrl,
|
||||
};
|
||||
});
|
||||
|
||||
// 滚动到顶部
|
||||
const scrollToTop = useCallback(() => {
|
||||
// 如果当前是列表页,触发列表页内部滚动
|
||||
|
||||
@@ -22,3 +22,135 @@ export const DECLAIMER = `
|
||||
发起人临时失联/爽约;发起人恶意删除队员,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: '本平台不对因网络故障、系统维护或不可抗力导致的服务中断承担责任。',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
border-bottom: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
padding: 8px 12px;
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -57,7 +59,9 @@
|
||||
align-items: flex-start;
|
||||
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -117,7 +121,9 @@
|
||||
align-items: center;
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "SF Compact Rounded";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
@@ -133,7 +139,9 @@
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "SF Compact Rounded";
|
||||
font-size: 22px;
|
||||
font-style: normal;
|
||||
@@ -154,7 +162,9 @@
|
||||
|
||||
.date {
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -164,7 +174,9 @@
|
||||
|
||||
.venueTime {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -191,7 +203,9 @@
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -235,7 +249,9 @@
|
||||
gap: 4px;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -251,7 +267,9 @@
|
||||
&Address {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-align: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -270,7 +288,9 @@
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
color: var(--Labels-Secondary, rgba(60, 60, 67, 0.6));
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -307,7 +327,9 @@
|
||||
|
||||
& > .buttonText {
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -347,7 +369,9 @@
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -373,7 +397,9 @@
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -383,7 +409,9 @@
|
||||
|
||||
.content {
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -396,13 +424,15 @@
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.orderNo {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
gap: 0px;
|
||||
|
||||
.copy {
|
||||
color: #007aff;
|
||||
@@ -421,7 +451,9 @@
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -442,7 +474,9 @@
|
||||
align-items: center;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -491,7 +525,9 @@
|
||||
&:nth-child(1) {
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -509,21 +545,160 @@
|
||||
.time {
|
||||
text-align: left;
|
||||
padding-left: 30px;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.rule {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
// .rule {
|
||||
// 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;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding-bottom: 100px;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
padding: 15px 0 0;
|
||||
@@ -531,7 +706,9 @@
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -567,7 +744,9 @@
|
||||
background: #000;
|
||||
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(16px);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -600,7 +779,9 @@
|
||||
text-align: center;
|
||||
// border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
color: #000;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -626,7 +807,9 @@
|
||||
padding: 12px 15px;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
text-align: center;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useRef } from "react";
|
||||
import { View, Text, Button, Image } from "@tarojs/components";
|
||||
import { Dialog } from "@nutui/nutui-react-taro";
|
||||
import Taro, { useDidShow, useRouter } from "@tarojs/taro";
|
||||
import dayjs from "dayjs";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import classnames from "classnames";
|
||||
import orderService, {
|
||||
@@ -10,6 +10,7 @@ import orderService, {
|
||||
GameOrderRes,
|
||||
OrderStatus,
|
||||
refundTextMap,
|
||||
RefundStatus,
|
||||
} from "@/services/orderService";
|
||||
import { debounce } from "@tarojs/runtime";
|
||||
import {
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
getOrderStatus,
|
||||
generateOrderActions,
|
||||
isPhoneNumber,
|
||||
genGameLength,
|
||||
} from "@/utils";
|
||||
import { getStorage, setStorage } from "@/store/storage";
|
||||
import { useGlobalStore } from "@/store/global";
|
||||
@@ -31,7 +33,7 @@ import img from "@/config/images";
|
||||
import CustomerIcon from "@/static/order/customer.svg";
|
||||
import { handleCustomerService } from "@/services/userService";
|
||||
import { requireLoginWithPhone } from "@/utils/helper";
|
||||
import { DECLAIMER } from "./config";
|
||||
import { RegistrationInstructions } from "./config";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
dayjs.locale("zh-cn");
|
||||
@@ -74,9 +76,20 @@ function genGameNotice(order_status, start_time) {
|
||||
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) {
|
||||
const { detail, currentLocation, orderDetail, init } = props;
|
||||
const { order_status, refund_status, amount } = orderDetail;
|
||||
const { order_status, refund_status, amount, refund_amount } = orderDetail;
|
||||
const {
|
||||
latitude,
|
||||
longitude,
|
||||
@@ -110,15 +123,17 @@ function GameInfo(props) {
|
||||
|
||||
const startTime = dayjs(start_time);
|
||||
const endTime = dayjs(end_time);
|
||||
const game_length = Number(
|
||||
(endTime.diff(startTime, "minutes") / 60).toFixed()
|
||||
);
|
||||
// const game_length = Number(
|
||||
// (endTime.diff(startTime, "minutes") / 60).toFixed(),
|
||||
// );
|
||||
const game_length = genGameLength(startTime, endTime);
|
||||
|
||||
const startMonth = startTime.format("M");
|
||||
const startDay = startTime.format("D");
|
||||
const theDayOfWeek = startTime.format("dddd");
|
||||
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);
|
||||
|
||||
@@ -244,7 +259,10 @@ function GameInfo(props) {
|
||||
<View className={styles.gameInfoContainer}>
|
||||
{["refund", "progress", "expired"].includes(orderStatus) && (
|
||||
<View className={styles.paidInfo}>
|
||||
{refundTextMap.get(refund_status)} ¥ {amount}
|
||||
{refundTextMap.get(refund_status)} ¥{" "}
|
||||
{[RefundStatus.PENDING, RefundStatus.SUCCESS].includes(refund_status)
|
||||
? refund_amount
|
||||
: amount}
|
||||
</View>
|
||||
)}
|
||||
{["progress", "expired"].includes(orderStatus) &&
|
||||
@@ -273,7 +291,7 @@ function GameInfo(props) {
|
||||
<View className={styles.gameInfoDateWeatherCalendarDateDate}>
|
||||
<View className={styles.date}>{startDate}</View>
|
||||
<View className={styles.venueTime}>
|
||||
{gameRange} ({game_length}小时)
|
||||
{gameRange} ({game_length})
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -344,7 +362,7 @@ function GameInfo(props) {
|
||||
handlePayNow: () => {},
|
||||
handleViewGame,
|
||||
},
|
||||
"detail"
|
||||
"detail",
|
||||
)?.map((obj) => (
|
||||
<View className={classnames(styles.button, styles[obj.className])}>
|
||||
<Text className={styles.buttonText}>{obj.text}</Text>
|
||||
@@ -504,7 +522,7 @@ function RefundPolicy(props) {
|
||||
const theTimeObj = dayjs(
|
||||
isLast
|
||||
? refund_policy.at(-2).deadline_formatted
|
||||
: item.deadline_formatted
|
||||
: item.deadline_formatted,
|
||||
);
|
||||
const year = theTimeObj.format("YYYY");
|
||||
const month = theTimeObj.format("M");
|
||||
@@ -531,7 +549,7 @@ function RefundPolicy(props) {
|
||||
className={classnames(
|
||||
styles.policyItem,
|
||||
targetIndex > index && index !== 0 ? styles.pastItem : "",
|
||||
targetIndex === index ? styles.currentItem : ""
|
||||
targetIndex === index ? styles.currentItem : "",
|
||||
)}
|
||||
>
|
||||
<View className={styles.time}>
|
||||
@@ -546,15 +564,66 @@ function RefundPolicy(props) {
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<Text className={styles.refundTip}>
|
||||
以上时间节点以提交申请时间为准。违约金由组织者(95%)与平台(5%)分配,用于补偿场地损失及处理成本。活动结束48小时后,1个工作日内系统自动结算至组织者账户。未申请退款直接缺席,报名费全额归组织者。
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function Disclaimer() {
|
||||
return (
|
||||
<View className={styles.declaimer}>
|
||||
<Text className={styles.title}>免责声明</Text>
|
||||
<Text className={styles.content}>{DECLAIMER}</Text>
|
||||
<View className={styles.disclaimer}>
|
||||
<View className={styles.disclaimerTitle}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import orderService, {
|
||||
OrderStatus,
|
||||
CancelType,
|
||||
refundTextMap,
|
||||
RefundStatus,
|
||||
} from "@/services/orderService";
|
||||
import { getStorage, removeStorage, setStorage } from "@/store/storage";
|
||||
import { useGlobalStore } from "@/store/global";
|
||||
@@ -101,7 +102,7 @@ const OrderList = () => {
|
||||
newList.splice(
|
||||
index,
|
||||
clear ? newList.length - index : 1,
|
||||
addPageInfo(res.data.rows, page)
|
||||
addPageInfo(res.data.rows, page),
|
||||
);
|
||||
return newList;
|
||||
});
|
||||
@@ -264,13 +265,17 @@ const OrderList = () => {
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuit(item) {
|
||||
async function handleQuit(item) {
|
||||
if (refundRef.current) {
|
||||
refundRef.current.show(item, (result) => {
|
||||
if (result) {
|
||||
getOrders(item.page);
|
||||
}
|
||||
});
|
||||
const res = await orderService.getRefundPolicy({ order_id: item.id });
|
||||
refundRef.current.show(
|
||||
{ ...item, refund_policy: res.data.refund_policy },
|
||||
(result) => {
|
||||
if (result) {
|
||||
getOrders(item.page);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +298,7 @@ const OrderList = () => {
|
||||
>
|
||||
<GeneralNavbar
|
||||
title="球局订单"
|
||||
backgroundColor="transparent"
|
||||
backgroundColor="#ffffff"
|
||||
titleClassName={styles.titleClassName}
|
||||
className={styles.navbar}
|
||||
/>
|
||||
@@ -316,7 +321,7 @@ const OrderList = () => {
|
||||
item.order_status === OrderStatus.PENDING &&
|
||||
item.cancel_type === CancelType.NONE;
|
||||
const canceled = [CancelType.USER, CancelType.TIMEOUT].includes(
|
||||
item.cancel_type
|
||||
item.cancel_type,
|
||||
);
|
||||
const { game_info } = item;
|
||||
|
||||
@@ -349,7 +354,7 @@ const OrderList = () => {
|
||||
<View
|
||||
className={classnames(
|
||||
styles.payNum,
|
||||
styles[unPay ? "pending" : "paid"]
|
||||
styles[unPay ? "pending" : "paid"],
|
||||
)}
|
||||
>
|
||||
<Text>
|
||||
@@ -358,7 +363,15 @@ const OrderList = () => {
|
||||
: refundTextMap.get(item.refund_status)}
|
||||
</Text>{" "}
|
||||
<View className={styles.amount}>
|
||||
¥ <Text>{item.amount}</Text>
|
||||
¥{" "}
|
||||
<Text>
|
||||
{[
|
||||
RefundStatus.PENDING,
|
||||
RefundStatus.SUCCESS,
|
||||
].includes(item.refund_status)
|
||||
? item.refund_amount
|
||||
: item.amount}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -370,7 +383,7 @@ const OrderList = () => {
|
||||
{insertDotInTags([location_name, court_type, "3.5km"]).map(
|
||||
(text, index) => (
|
||||
<Text key={index}>{text}</Text>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</View>
|
||||
<View className={styles.gameOtherInfo}>
|
||||
@@ -426,12 +439,12 @@ const OrderList = () => {
|
||||
handlePayNow,
|
||||
handleViewGame,
|
||||
},
|
||||
"list"
|
||||
"list",
|
||||
)?.map((obj) => (
|
||||
<View
|
||||
className={classnames(
|
||||
styles.button,
|
||||
styles[obj.className]
|
||||
styles[obj.className],
|
||||
)}
|
||||
>
|
||||
<Text className={styles.buttonText}>{obj.text}</Text>
|
||||
|
||||
@@ -77,7 +77,7 @@ const CommentReply = () => {
|
||||
if (allCommentIds.length > 0) {
|
||||
// 使用统一接口标记已读,传入所有评论ID
|
||||
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||
console.error("标记评论已读失败:", e);
|
||||
console.warn("标记评论已读失败:", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ const CommentReply = () => {
|
||||
|
||||
if (allCommentIds.length > 0) {
|
||||
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||
console.error("标记评论已读失败:", e);
|
||||
console.warn("标记评论已读失败:", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const EnableNotificationPage: React.FC = () => {
|
||||
setQrCodeUrl(res.data.ServiceAccountQRCode);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取二维码失败:', error);
|
||||
console.warn('获取二维码失败:', error);
|
||||
}
|
||||
};
|
||||
fetchQRCode();
|
||||
|
||||
@@ -62,7 +62,7 @@ const NewFollow = () => {
|
||||
if (allFanIds.length > 0) {
|
||||
// 使用统一接口标记已读,传入所有关注者ID
|
||||
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||
console.error("标记关注已读失败:", e);
|
||||
console.warn("标记关注已读失败:", e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -168,7 +168,7 @@ const NewFollow = () => {
|
||||
|
||||
if (allFanIds.length > 0) {
|
||||
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||
console.error("标记关注已读失败:", e);
|
||||
console.warn("标记关注已读失败:", e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -99,7 +99,7 @@ function isOnCancelEmpty(onCancelFunc) {
|
||||
const normalized = funcString.replace(/\s/g, "");
|
||||
return emptyFunctionPatterns.includes(normalized);
|
||||
} catch (error) {
|
||||
console.error("检查 onCancel 函数时出错:", error);
|
||||
console.warn("检查 onCancel 函数时出错:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -531,7 +531,7 @@ function Result() {
|
||||
// setQrCodeUrl(tempFilePath);
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error("获取二维码失败:", error);
|
||||
console.warn("获取二维码失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +645,7 @@ function Result() {
|
||||
});
|
||||
return imageUrl;
|
||||
} catch (error) {
|
||||
console.error("生成图片失败:", error);
|
||||
console.warn("生成图片失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取剪切板失败:', error)
|
||||
console.warn('获取剪切板失败:', error)
|
||||
Taro.showToast({
|
||||
title: '读取剪切板失败,请手动输入',
|
||||
icon: 'error',
|
||||
@@ -163,7 +163,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择图片失败:', error)
|
||||
console.warn('选择图片失败:', error)
|
||||
if (!(typeof error === 'object' && error.errMsg && error.errMsg.includes('fail cancel'))) {
|
||||
setUploadFailCount(prev => prev + 1)
|
||||
Taro.showToast({
|
||||
|
||||
@@ -5,6 +5,7 @@ import img from '@/config/images';
|
||||
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
|
||||
import SelectStadium from '../SelectStadium/SelectStadium'
|
||||
import { Stadium } from '../SelectStadium/StadiumDetail'
|
||||
import { normalize_address } from '@/utils/locationUtils'
|
||||
import './FormBasicInfo.scss'
|
||||
|
||||
type PlayGame = {
|
||||
@@ -54,7 +55,7 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
|
||||
onChange({...value,
|
||||
venue_id,
|
||||
location_name: name,
|
||||
location: address,
|
||||
location: normalize_address(address || ''),
|
||||
latitude,
|
||||
longitude,
|
||||
court_type,
|
||||
|
||||
@@ -4,7 +4,7 @@ import Taro from '@tarojs/taro'
|
||||
import { Loading } from '@nutui/nutui-react-taro'
|
||||
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
|
||||
import { CommonPopup, CustomPopup } from '../../../../components'
|
||||
import { getLocation } from '@/utils/locationUtils'
|
||||
import { getLocation, normalize_address } from '@/utils/locationUtils'
|
||||
import PublishService from '@/services/publishService'
|
||||
import images from '@/config/images'
|
||||
import './SelectStadium.scss'
|
||||
@@ -53,7 +53,7 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取场馆列表失败:', error)
|
||||
console.warn('获取场馆列表失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -100,14 +100,14 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
success: (res) => {
|
||||
setSelectedStadium({
|
||||
name: res.name,
|
||||
address: res.address,
|
||||
address: normalize_address(res.address || ''),
|
||||
longitude: res.longitude,
|
||||
latitude: res.latitude
|
||||
})
|
||||
setShowDetail(true)
|
||||
},
|
||||
fail: (err: { errMsg: string }) => {
|
||||
console.error('选择位置失败:', err)
|
||||
console.warn('选择位置失败:', err)
|
||||
const { errMsg } = err || {};
|
||||
if (!errMsg.includes('fail cancel')) {
|
||||
Taro.showToast({
|
||||
|
||||
@@ -7,6 +7,7 @@ import TextareaTag from '@/components/TextareaTag'
|
||||
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
||||
import { useKeyboardHeight } from '@/store/keyboardStore'
|
||||
import { useDictionaryActions } from '@/store/dictionaryStore'
|
||||
import { normalize_address } from '@/utils/locationUtils'
|
||||
|
||||
import './StadiumDetail.scss'
|
||||
|
||||
@@ -145,14 +146,14 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
setFormData({
|
||||
...formData,
|
||||
name: res.name,
|
||||
address: res.address,
|
||||
address: normalize_address(res.address || ''),
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
istance: null
|
||||
})
|
||||
},
|
||||
fail: (err: { errMsg: string }) => {
|
||||
console.error('选择位置失败:', err)
|
||||
console.warn('选择位置失败:', err)
|
||||
const { errMsg } = err || {};
|
||||
if (!errMsg.includes('fail cancel')) {
|
||||
Taro.showToast({
|
||||
@@ -199,13 +200,6 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
}
|
||||
}
|
||||
|
||||
// 当键盘显示时触发 changeTextarea
|
||||
useEffect(() => {
|
||||
if (isKeyboardVisible) {
|
||||
changeTextarea(true)
|
||||
}
|
||||
}, [isKeyboardVisible])
|
||||
|
||||
const changePicker = (value:boolean) => {
|
||||
setOpenPicker(value);
|
||||
}
|
||||
@@ -237,7 +231,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
<View className='stadium-item-right'>
|
||||
<View className='stadium-name'>{formData.name}</View>
|
||||
<View className='stadium-address'>
|
||||
<Text>{calculateDistance(formData.istance || null)} · </Text>
|
||||
<Text>{calculateDistance(formData.istance || null) + ' · '}</Text>
|
||||
<Text>{formData.address}</Text>
|
||||
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||
</View>
|
||||
@@ -269,11 +263,13 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
<TextareaTag
|
||||
value={formData[item.prop]}
|
||||
onChange={(value) => {
|
||||
//changeTextarea(true)
|
||||
updateFormData(item.prop, value)
|
||||
}}
|
||||
// onBlur={() => changeTextarea(false)}
|
||||
onFocus={() => changeTextarea(true)}
|
||||
// onBlur={() => {
|
||||
// }}
|
||||
onFocus={() => {
|
||||
changeTextarea(true)
|
||||
}}
|
||||
placeholder='有其他场地信息可备注'
|
||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from "../../config/formSchema/publishBallFormSchema";
|
||||
import { PublishBallFormData } from "../../../types/publishBall";
|
||||
import PublishService from "@/services/publishService";
|
||||
import { getNextHourTime, getEndTime, delay } from "@/utils";
|
||||
import { getNextHourTime, getEndTime, delay, getBackendErrorMsg } from "@/utils";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import GeneralNavbar from "@/components/GeneralNavbar";
|
||||
import images from "@/config/images";
|
||||
@@ -364,84 +364,79 @@ const PublishBall: React.FC = () => {
|
||||
};
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
// 基础验证
|
||||
const params = getParams();
|
||||
const { republish } = params || {};
|
||||
if (activityType === "individual") {
|
||||
const isValid = validateFormData(formData[0]);
|
||||
if (!isValid || publishLoading) {
|
||||
return;
|
||||
}
|
||||
if (!isValid || publishLoading) return;
|
||||
setPublishLoading(true);
|
||||
const {
|
||||
activityInfo,
|
||||
descriptionInfo,
|
||||
is_substitute_supported,
|
||||
timeRange,
|
||||
players,
|
||||
skill_level,
|
||||
image_list,
|
||||
wechat,
|
||||
id,
|
||||
...rest
|
||||
} = formData[0];
|
||||
const { min, max, organizer_joined } = players;
|
||||
const options = {
|
||||
...rest,
|
||||
...activityInfo,
|
||||
...descriptionInfo,
|
||||
...timeRange,
|
||||
max_players: max,
|
||||
min_players: min,
|
||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||
skill_level_min: skill_level[0],
|
||||
skill_level_max: skill_level[1],
|
||||
image_list: image_list.map((item) => item.url),
|
||||
is_wechat_contact: wechat.is_wechat_contact ? 1 : 0,
|
||||
wechat_contact: wechat.wechat_contact || wechat.default_wechat_contact,
|
||||
is_substitute_supported: is_substitute_supported ? "1" : "0",
|
||||
...(republish === "0" ? { id } : {}),
|
||||
};
|
||||
const res =
|
||||
republish === "0"
|
||||
? await PublishService.gamesUpdate(options)
|
||||
: await PublishService.createPersonal(options);
|
||||
const successText = republish === "0" ? "更新成功" : "发布成功";
|
||||
if (res.code === 0 && res.data) {
|
||||
Taro.showToast({
|
||||
title: successText,
|
||||
icon: "success",
|
||||
});
|
||||
delay(1000);
|
||||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||||
const id = (res as any).data?.id;
|
||||
// 如果是编辑,就返回,否则就是新发布
|
||||
if (republish === "0") {
|
||||
Taro.navigateBack();
|
||||
try {
|
||||
const {
|
||||
activityInfo,
|
||||
descriptionInfo,
|
||||
is_substitute_supported,
|
||||
timeRange,
|
||||
players,
|
||||
skill_level,
|
||||
image_list,
|
||||
wechat,
|
||||
id,
|
||||
title,
|
||||
...rest
|
||||
} = formData[0];
|
||||
const { min, max, organizer_joined } = players;
|
||||
const options = {
|
||||
...rest,
|
||||
...activityInfo,
|
||||
...descriptionInfo,
|
||||
...timeRange,
|
||||
title: title?.replace(/\n/g, ''),
|
||||
max_players: max,
|
||||
min_players: min,
|
||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||
skill_level_min: skill_level[0],
|
||||
skill_level_max: skill_level[1],
|
||||
image_list: image_list.map((item) => item.url),
|
||||
is_wechat_contact: wechat.is_wechat_contact ? 1 : 0,
|
||||
wechat_contact: wechat.wechat_contact || wechat.default_wechat_contact,
|
||||
is_substitute_supported: is_substitute_supported ? "1" : "0",
|
||||
...(republish === "0" ? { id } : {}),
|
||||
};
|
||||
const res =
|
||||
republish === "0"
|
||||
? await PublishService.gamesUpdate(options)
|
||||
: await PublishService.createPersonal(options);
|
||||
const successText = republish === "0" ? "更新成功" : "发布成功";
|
||||
if (res.code === 0 && res.data) {
|
||||
Taro.showToast({ title: successText, icon: "success" });
|
||||
delay(1000);
|
||||
const id = (res as any).data?.id;
|
||||
if (republish === "0") {
|
||||
Taro.navigateBack();
|
||||
} else {
|
||||
Taro.redirectTo({
|
||||
url: `/game_pages/detail/index?id=${id || 1}&from=publish&autoShare=1`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 使用 redirectTo 替换当前页面,避免返回时回到发布页面
|
||||
Taro.redirectTo({
|
||||
// @ts-expect-error: id
|
||||
url: `/game_pages/detail/index?id=${
|
||||
id || 1
|
||||
}&from=publish&autoShare=1`,
|
||||
Taro.showToast({
|
||||
title: getBackendErrorMsg(res, "发布失败"),
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} else {
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: res.message,
|
||||
title: getBackendErrorMsg(error, "发布失败"),
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (activityType === "group") {
|
||||
const isValid = formData.every((item) => validateFormData(item));
|
||||
if (!isValid || publishLoading) {
|
||||
return;
|
||||
}
|
||||
setPublishLoading(true);
|
||||
if (!isValid || publishLoading) return;
|
||||
if (checkAdjacentDataSame(formData)) {
|
||||
Taro.showToast({
|
||||
title: "信息不可与前序场完全一致",
|
||||
@@ -449,60 +444,62 @@ const PublishBall: React.FC = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const options = formData.map((item) => {
|
||||
const {
|
||||
activityInfo,
|
||||
descriptionInfo,
|
||||
timeRange,
|
||||
players,
|
||||
skill_level,
|
||||
is_substitute_supported,
|
||||
id,
|
||||
...rest
|
||||
} = item;
|
||||
const { min, max, organizer_joined } = players;
|
||||
return {
|
||||
...rest,
|
||||
...activityInfo,
|
||||
...descriptionInfo,
|
||||
...timeRange,
|
||||
max_players: max,
|
||||
min_players: min,
|
||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||
skill_level_min: skill_level[0],
|
||||
skill_level_max: skill_level[1],
|
||||
is_substitute_supported: is_substitute_supported ? "1" : "0",
|
||||
image_list: item.image_list.map((img) => img.url),
|
||||
...(republish === "0" ? { id } : {}),
|
||||
};
|
||||
});
|
||||
const successText = republish === "0" ? "更新成功" : "发布成功";
|
||||
const res =
|
||||
republish === "0"
|
||||
? await PublishService.gamesUpdate(options[0])
|
||||
: await PublishService.create_play_pmoothlys({ rows: options });
|
||||
if (res.code === 0 && res.data) {
|
||||
Taro.showToast({
|
||||
title: successText,
|
||||
icon: "success",
|
||||
setPublishLoading(true);
|
||||
try {
|
||||
const options = formData.map((item) => {
|
||||
const {
|
||||
activityInfo,
|
||||
descriptionInfo,
|
||||
timeRange,
|
||||
players,
|
||||
skill_level,
|
||||
is_substitute_supported,
|
||||
id,
|
||||
title,
|
||||
...rest
|
||||
} = item;
|
||||
const { min, max, organizer_joined } = players;
|
||||
return {
|
||||
...rest,
|
||||
...activityInfo,
|
||||
...descriptionInfo,
|
||||
...timeRange,
|
||||
title: title?.replace(/\n/g, ' '),
|
||||
max_players: max,
|
||||
min_players: min,
|
||||
organizer_joined: organizer_joined === true ? 1 : 0,
|
||||
skill_level_min: skill_level[0],
|
||||
skill_level_max: skill_level[1],
|
||||
is_substitute_supported: is_substitute_supported ? "1" : "0",
|
||||
image_list: item.image_list.map((img) => img.url),
|
||||
...(republish === "0" ? { id } : {}),
|
||||
};
|
||||
});
|
||||
delay(1000);
|
||||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||||
const id =
|
||||
const successText = republish === "0" ? "更新成功" : "发布成功";
|
||||
const res =
|
||||
republish === "0"
|
||||
? (res as any).data?.id
|
||||
: (res as any).data?.[0]?.id;
|
||||
// 使用 redirectTo 替换当前页面,避免返回时回到发布页面
|
||||
Taro.redirectTo({
|
||||
// @ts-expect-error: id
|
||||
url: `/game_pages/detail/index?id=${
|
||||
id || 1
|
||||
}&from=publish&autoShare=1`,
|
||||
});
|
||||
} else {
|
||||
? await PublishService.gamesUpdate(options[0])
|
||||
: await PublishService.create_play_pmoothlys({ rows: options });
|
||||
if (res.code === 0 && res.data) {
|
||||
Taro.showToast({ title: successText, icon: "success" });
|
||||
delay(1000);
|
||||
const id =
|
||||
republish === "0"
|
||||
? (res as any).data?.id
|
||||
: (res as any).data?.[0]?.id;
|
||||
Taro.redirectTo({
|
||||
url: `/game_pages/detail/index?id=${id || 1}&from=publish&autoShare=1`,
|
||||
});
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: getBackendErrorMsg(res, "发布失败"),
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: res.message,
|
||||
title: getBackendErrorMsg(error, "发布失败"),
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
|
||||
@@ -169,7 +169,7 @@ class GameDetailService {
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '获取互关列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取互关列表失败:', error);
|
||||
console.warn('获取互关列表失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '获取粉丝列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取粉丝列表失败:', error);
|
||||
console.warn('获取粉丝列表失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '获取新增粉丝列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取新增粉丝列表失败:', error);
|
||||
console.warn('获取新增粉丝列表失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '获取关注列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取关注列表失败:', error);
|
||||
console.warn('获取关注列表失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '关注失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('关注失败:', error);
|
||||
console.warn('关注失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '取消关注失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消关注失败:', error);
|
||||
console.warn('取消关注失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '获取推荐用户失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取推荐用户失败:', error);
|
||||
console.warn('获取推荐用户失败:', error);
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,7 @@ export class FollowService {
|
||||
return 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查关注状态失败:', error);
|
||||
console.warn('检查关注状态失败:', error);
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ export class FollowService {
|
||||
throw new Error(response.message || '屏蔽推荐用户失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('屏蔽推荐用户失败:', error);
|
||||
console.warn('屏蔽推荐用户失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,9 +140,13 @@ class HttpService {
|
||||
this.hideLoadingTimer = null
|
||||
}
|
||||
|
||||
// 延时300ms后隐藏loading,避免频繁切换
|
||||
// 延时 800ms 后隐藏 loading,避免频繁切换;捕获 hideLoading 失败(如 toast 已被关闭)避免报错打断流程
|
||||
this.hideLoadingTimer = setTimeout(() => {
|
||||
Taro.hideLoading()
|
||||
try {
|
||||
Taro.hideLoading()
|
||||
} catch (e) {
|
||||
// 忽略 "toast can't be found" 等,避免发布后分享等流程报错
|
||||
}
|
||||
this.currentLoadingText = ''
|
||||
this.hideLoadingTimer = null
|
||||
}, 800)
|
||||
|
||||
@@ -27,7 +27,7 @@ export const getGamesList = async (params?: Record<string, any>) => {
|
||||
// const fetchApi = isIntegrate ? '/games/integrate_list' : '/games/list'
|
||||
return httpService.post('/games/list', params, { showLoading: false })
|
||||
} catch (error) {
|
||||
console.error("列表数据获取失败:", error);
|
||||
console.warn("列表数据获取失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -41,7 +41,7 @@ export const getGamesIntegrateList = async (params?: Record<string, any>) => {
|
||||
try {
|
||||
return httpService.post('/games/integrate_list', params, { showLoading: false })
|
||||
} catch (error) {
|
||||
console.error("列表数据获取失败:", error);
|
||||
console.warn("列表数据获取失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -55,7 +55,7 @@ export const getGamesCount = async (params?: Record<string, any>) => {
|
||||
try {
|
||||
return httpService.post('/games/count', params, { showLoading: false })
|
||||
} catch (error) {
|
||||
console.error("列表数量获取失败:", error);
|
||||
console.warn("列表数量获取失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -71,7 +71,7 @@ export const getSearchHistory = async (params) => {
|
||||
return httpService.post('/games/search_history', params, { showLoading: false })
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("历史记录获取失败:", error);
|
||||
console.warn("历史记录获取失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export const clearHistory = async (params) => {
|
||||
return httpService.post('/games/search_history/delete_all', params, { showLoading: false })
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("清除历史记录失败:", error);
|
||||
console.warn("清除历史记录失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export const searchSuggestion = async (params) => {
|
||||
return httpService.post('/games/search_recommendations', params)
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("搜索建议获取失败:", error);
|
||||
console.warn("搜索建议获取失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export const getCities = async () => {
|
||||
return httpService.post('/cities/tree', {})
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("城市列表获取失败:", error);
|
||||
console.warn("城市列表获取失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export const getCityQrCode = async () => {
|
||||
return httpService.post('/hot_city_qr/list', {})
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("城市二维码获取失败:", error);
|
||||
console.warn("城市二维码获取失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
@@ -140,7 +140,7 @@ export const getDistricts = async (params: { province: string; city: string }) =
|
||||
return httpService.post('/cities/cities', params)
|
||||
} catch (error) {
|
||||
// 捕获并打印错误信息
|
||||
console.error("行政区列表获取失败:", error);
|
||||
console.warn("行政区列表获取失败:", error);
|
||||
// 抛出错误以便上层处理
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export const wechat_auth_login = async (
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("微信授权登录失败:", error);
|
||||
console.warn("微信授权登录失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "微信授权失败,请重试",
|
||||
@@ -160,7 +160,7 @@ export const phone_auth_login = async (
|
||||
await useUser.getState().fetchUserInfo();
|
||||
await useUser.getState().checkNicknameChangeStatus();
|
||||
} catch (error) {
|
||||
console.error("更新用户信息到 store 失败:", error);
|
||||
console.warn("更新用户信息到 store 失败:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -178,7 +178,7 @@ export const phone_auth_login = async (
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("手机号登录失败:", error);
|
||||
console.warn("手机号登录失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error.message,
|
||||
@@ -206,7 +206,7 @@ export const send_sms_code = async (phone: string): Promise<SmsResponse> => {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("发送短信失败:", error);
|
||||
console.warn("发送短信失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error.message,
|
||||
@@ -232,7 +232,7 @@ export const verify_sms_code = async (
|
||||
user_info: response.data?.userInfo,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("验证验证码失败:", error);
|
||||
console.warn("验证验证码失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error.message,
|
||||
@@ -255,7 +255,7 @@ export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
||||
Taro.setStorageSync("is_logged_in", true);
|
||||
Taro.setStorageSync("login_time", Date.now());
|
||||
} catch (error) {
|
||||
console.error("保存登录状态失败:", error);
|
||||
console.warn("保存登录状态失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -270,7 +270,7 @@ export const clear_login_state = () => {
|
||||
Taro.removeStorageSync("is_logged_in");
|
||||
Taro.removeStorageSync("login_time");
|
||||
} catch (error) {
|
||||
console.error("清除登录状态失败:", error);
|
||||
console.warn("清除登录状态失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -374,7 +374,7 @@ export const refresh_login_status = async (): Promise<boolean> => {
|
||||
// 检查本地存储的登录状态
|
||||
return check_login_status();
|
||||
} catch (error) {
|
||||
console.error("刷新登录状态失败:", error);
|
||||
console.warn("刷新登录状态失败:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -385,7 +385,7 @@ export const updateUserPhone = async (payload: ChangePhoneParams) => {
|
||||
const response = await httpService.post("/user/update_phone", payload);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("更新用户手机号失败:", error);
|
||||
console.warn("更新用户手机号失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -402,7 +402,7 @@ export const getUserInfoById = async (id) => {
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -415,7 +415,7 @@ export const followUser = async (following_id) => {
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("关注失败:", error);
|
||||
console.warn("关注失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -428,7 +428,7 @@ export const unFollowUser = async (following_id) => {
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("取消关注失败:", error);
|
||||
console.warn("取消关注失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -464,7 +464,7 @@ export const silentLogin = async (): Promise<LoginResponse> => {
|
||||
console.log("微信登录结果:", login_result);
|
||||
|
||||
if (!login_result.code) {
|
||||
console.error("微信登录失败:未获取到code");
|
||||
console.warn("微信登录失败:未获取到code");
|
||||
return {
|
||||
success: false,
|
||||
message: "微信登录失败",
|
||||
@@ -506,14 +506,14 @@ export const silentLogin = async (): Promise<LoginResponse> => {
|
||||
user_info,
|
||||
};
|
||||
} else {
|
||||
console.error("静默登录失败:", auth_response.message);
|
||||
console.warn("静默登录失败:", auth_response.message);
|
||||
return {
|
||||
success: false,
|
||||
message: auth_response.message || "静默登录失败",
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("静默登录异常:", error);
|
||||
console.warn("静默登录异常:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "静默登录失败,请重试",
|
||||
|
||||
@@ -188,6 +188,21 @@ class OrderService {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 获取退款政策
|
||||
async getRefundPolicy({
|
||||
order_id,
|
||||
}: {
|
||||
order_id: number;
|
||||
}): Promise<ApiResponse<any>> {
|
||||
return httpService.post(
|
||||
"/payment/order_refund_policy",
|
||||
{ order_id },
|
||||
{
|
||||
showLoading: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出认证服务实例
|
||||
|
||||
@@ -39,19 +39,28 @@ export interface uploadFileResponseData {
|
||||
updated_at: string,
|
||||
}
|
||||
|
||||
// 从上传错误中取出可展示的文案
|
||||
function get_upload_error_msg(error: any): string {
|
||||
if (!error) return "上传失败";
|
||||
const msg =
|
||||
error?.message ||
|
||||
(typeof error?.error === "string" ? error.error : error?.error?.message) ||
|
||||
(error?.data?.message ?? error?.data?.msg) ||
|
||||
"";
|
||||
return (msg && String(msg).trim()) || "上传失败";
|
||||
}
|
||||
|
||||
// 发布球局类
|
||||
class UploadApi {
|
||||
async upload(req: UploadFilesData): Promise<{ id: string, data: uploadFileResponseData }> {
|
||||
|
||||
let fullUrl = `${envConfig.apiBaseURL}/api/gallery/upload`
|
||||
|
||||
const authHeader = tokenManager.getAuthHeader()
|
||||
const { id, ...rest } = req
|
||||
const fullUrl = `${envConfig.apiBaseURL}/api/gallery/upload`;
|
||||
const authHeader = tokenManager.getAuthHeader();
|
||||
const { id, ...rest } = req;
|
||||
try {
|
||||
const res = await Taro.uploadFile({
|
||||
url: fullUrl,
|
||||
filePath: rest.filePath,
|
||||
name: 'file',
|
||||
name: "file",
|
||||
formData: {
|
||||
description: rest.description,
|
||||
tags: rest.tags,
|
||||
@@ -59,12 +68,17 @@ class UploadApi {
|
||||
},
|
||||
header: authHeader,
|
||||
});
|
||||
return {
|
||||
id,
|
||||
data: JSON.parse(res.data).data,
|
||||
const parsed = JSON.parse(res.data);
|
||||
if (parsed.code !== 0) {
|
||||
const msg = get_upload_error_msg(parsed);
|
||||
Taro.showToast({ title: msg, icon: "none" });
|
||||
throw new Error(msg);
|
||||
}
|
||||
return { id, data: parsed.data };
|
||||
} catch (error) {
|
||||
throw { id, error }
|
||||
const msg = get_upload_error_msg(error);
|
||||
Taro.showToast({ title: msg, icon: "none" });
|
||||
throw { id, error };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +117,7 @@ class UploadApi {
|
||||
throw new Error(result.message || '上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传图片失败:', error);
|
||||
console.warn('上传图片失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ export class UserService {
|
||||
throw new Error(response.message || "获取用户信息失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
// 返回默认用户信息
|
||||
return {} as UserInfo;
|
||||
}
|
||||
@@ -392,7 +392,7 @@ export class UserService {
|
||||
throw new Error(response.message || "更新用户信息失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("更新用户信息失败:", error);
|
||||
console.warn("更新用户信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -417,7 +417,7 @@ export class UserService {
|
||||
throw new Error(response.message || "获取主办球局失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取主办球局失败:", error);
|
||||
console.warn("获取主办球局失败:", error);
|
||||
// 返回符合ListContainer data格式的模拟数据
|
||||
return [];
|
||||
}
|
||||
@@ -443,7 +443,7 @@ export class UserService {
|
||||
throw new Error(response.message || "获取参与球局失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取参与球局失败:", error);
|
||||
console.warn("获取参与球局失败:", error);
|
||||
// 返回符合ListContainer data格式的模拟数据
|
||||
return [];
|
||||
}
|
||||
@@ -485,7 +485,7 @@ export class UserService {
|
||||
throw new Error(response.message || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("关注操作失败:", error);
|
||||
console.warn("关注操作失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +543,7 @@ export class UserService {
|
||||
throw new Error(response.message || "更新用户信息失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("保存用户信息失败:", error);
|
||||
console.warn("保存用户信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -573,26 +573,16 @@ export class UserService {
|
||||
throw new Error(response.message || "获取用户动态失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取用户动态失败:", error);
|
||||
console.warn("获取用户动态失败:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 上传头像
|
||||
// 上传头像:仅在上传成功且 save 成功时返回 ossPath;失败则抛出,由调用方处理,避免误调 user/update
|
||||
static async upload_avatar(file_path: string): Promise<string> {
|
||||
try {
|
||||
// 先上传文件到服务器
|
||||
const result = await uploadFiles.upload_oss_img(file_path);
|
||||
|
||||
await this.save_user_info({ avatar: result.ossPath });
|
||||
|
||||
// 使用新的响应格式中的file_url字段
|
||||
return result.ossPath;
|
||||
} catch (error) {
|
||||
console.error("头像上传失败:", error);
|
||||
// 如果上传失败,返回默认头像
|
||||
return require("../static/userInfo/default_avatar.svg");
|
||||
}
|
||||
const result = await uploadFiles.upload_oss_img(file_path);
|
||||
await this.save_user_info({ avatar: result.ossPath });
|
||||
return result.ossPath;
|
||||
}
|
||||
|
||||
// 解析用户手机号
|
||||
@@ -612,7 +602,7 @@ export class UserService {
|
||||
throw new Error(response.message || "获取手机号失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取手机号失败:", error);
|
||||
console.warn("获取手机号失败:", error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -628,7 +618,7 @@ export class UserService {
|
||||
throw new Error(message || "获取职业树失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取职业树失败:", error);
|
||||
console.warn("获取职业树失败:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -644,7 +634,7 @@ export class UserService {
|
||||
throw new Error(message || "获取城市树失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取职业树失败:", error);
|
||||
console.warn("获取职业树失败:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -665,7 +655,7 @@ export class UserService {
|
||||
throw new Error(message || "注销账户失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("注销账户失败:", error);
|
||||
console.warn("注销账户失败:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,7 +670,7 @@ export const fetchUserProfile = async (): Promise<
|
||||
const response = await httpService.post("user/detail");
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -695,7 +685,7 @@ export const checkNicknameChangeStatus = async (): Promise<
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("获取昵称修改状态失败:", error);
|
||||
console.warn("获取昵称修改状态失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -707,7 +697,7 @@ export const updateNickname = async (nickname: string) => {
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("昵称修改失败:", error);
|
||||
console.warn("昵称修改失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -718,7 +708,7 @@ export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
|
||||
const response = await httpService.post("/user/update", payload);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("更新用户信息失败:", error);
|
||||
console.warn("更新用户信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -737,7 +727,7 @@ export const updateUserLocation = async (
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("更新用户坐标位置失败:", error);
|
||||
console.warn("更新用户坐标位置失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -774,13 +764,13 @@ export const handleCustomerService = async (): Promise<void> => {
|
||||
console.log("打开客服成功:", res);
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error("打开客服失败:", error);
|
||||
console.warn("打开客服失败:", error);
|
||||
// 如果官方客服不可用,显示备用联系方式
|
||||
showCustomerServiceFallback(customerService);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("客服功能异常:", error);
|
||||
console.warn("客服功能异常:", error);
|
||||
// 备用方案:显示联系信息
|
||||
showCustomerServiceFallback();
|
||||
}
|
||||
@@ -810,7 +800,7 @@ const showCustomerServiceFallback = (customerInfo?: any) => {
|
||||
phoneNumber: customerInfo.phoneNumber,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("拨打电话失败:", error);
|
||||
console.warn("拨打电话失败:", error);
|
||||
Taro.showToast({
|
||||
title: "拨打电话失败",
|
||||
icon: "none",
|
||||
@@ -827,7 +817,7 @@ const showCustomerServiceFallback = (customerInfo?: any) => {
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("复制邮箱失败:", error);
|
||||
console.warn("复制邮箱失败:", error);
|
||||
Taro.showToast({
|
||||
title: "复制失败",
|
||||
icon: "none",
|
||||
|
||||
@@ -39,7 +39,7 @@ export class WalletService {
|
||||
throw new Error(response.message || "获取钱包信息失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取钱包信息失败:", error);
|
||||
console.warn("获取钱包信息失败:", error);
|
||||
// 返回模拟数据
|
||||
return {
|
||||
balance: 1588.80,
|
||||
@@ -62,7 +62,7 @@ export class WalletService {
|
||||
throw new Error(response.message || "获取交易记录失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取交易记录失败:", error);
|
||||
console.warn("获取交易记录失败:", error);
|
||||
// 返回模拟数据
|
||||
return [
|
||||
{
|
||||
@@ -107,7 +107,7 @@ export class WalletService {
|
||||
throw new Error(response.message || "提现申请提交失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("提现申请提交失败:", error);
|
||||
console.warn("提现申请提交失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
13
src/static/list/icon_game_type.svg
Normal file
13
src/static/list/icon_game_type.svg
Normal 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 |
@@ -1,11 +1,18 @@
|
||||
<svg width="24" height="32" viewBox="0 0 24 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7504_18610)">
|
||||
<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"/>
|
||||
<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"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7504_18610">
|
||||
<rect width="24" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 74.87 89.85">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #e6ff54;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="_图层_1-2" data-name=" 图层 1">
|
||||
<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 |
@@ -93,7 +93,7 @@ function MyComponent() {
|
||||
|
||||
console.log('请求成功!')
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error)
|
||||
console.warn('请求失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@@ -35,17 +35,22 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
set({ isLoading: true, error: null })
|
||||
|
||||
try {
|
||||
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip,supported_cities,bannerListImage,bannerDetailImage,bannerListIndex';
|
||||
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip,supported_cities,show_home_qrcode,bannerListImage,bannerDetailImage,bannerListIndex';
|
||||
const response = await commonApi.getDictionaryManyKey(keys)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
const dictionaryData = {};
|
||||
const dictionaryData: DictionaryData = {};
|
||||
keys.split(',').forEach(key => {
|
||||
const list = response.data[key];
|
||||
const raw = response.data[key];
|
||||
// 单值配置:首页是否展示二维码(1/0 或 true/false)
|
||||
if (key === 'show_home_qrcode') {
|
||||
dictionaryData[key] = raw === '1' || raw === 1 || raw === true;
|
||||
return;
|
||||
}
|
||||
// supported_cities 格式如 "上海市||北京市",用 || 分割
|
||||
const listData = key === 'supported_cities'
|
||||
? (list ? String(list).split('||').map((s) => s.trim()).filter(Boolean) : [])
|
||||
: (list ? list.split('|') : []);
|
||||
? (raw ? String(raw).split('||').map((s) => s.trim()).filter(Boolean) : [])
|
||||
: (raw ? String(raw).split('|') : []);
|
||||
dictionaryData[key] = listData;
|
||||
})
|
||||
set({
|
||||
@@ -71,7 +76,7 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
error: errorMessage,
|
||||
isLoading: false
|
||||
})
|
||||
console.error('获取字典数据失败:', error)
|
||||
console.warn('获取字典数据失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export const useKeyboardStore = create<KeyboardStore>((set, get) => ({
|
||||
try {
|
||||
listener(height, true)
|
||||
} catch (error) {
|
||||
console.error('键盘监听器执行错误:', error)
|
||||
console.warn('键盘监听器执行错误:', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -92,7 +92,7 @@ export const useKeyboardStore = create<KeyboardStore>((set, get) => ({
|
||||
try {
|
||||
listener(0, false)
|
||||
} catch (error) {
|
||||
console.error('键盘监听器执行错误:', error)
|
||||
console.warn('键盘监听器执行错误:', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -150,12 +150,13 @@ export const useKeyboardStore = create<KeyboardStore>((set, get) => ({
|
||||
|
||||
// 导出便捷的 hooks
|
||||
export const useKeyboardHeight = () => {
|
||||
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardStore()
|
||||
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardStore()
|
||||
|
||||
return {
|
||||
keyboardHeight,
|
||||
isKeyboardVisible,
|
||||
addListener,
|
||||
initializeKeyboardListener
|
||||
initializeKeyboardListener,
|
||||
setKeyboardVisible
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
console.error("更新列表数据失败:", error);
|
||||
console.warn("更新列表数据失败:", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
@@ -724,7 +724,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error("获取行政区列表失败:", error);
|
||||
console.warn("获取行政区列表失败:", error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ export const useMessageStore = create<MessageStore>()((set, get) => ({
|
||||
set({ loading: false });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取红点信息失败:", e);
|
||||
console.warn("获取红点信息失败:", e);
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const setStorage = (key: string, data: any) => {
|
||||
try {
|
||||
Taro.setStorageSync(key, JSON.stringify(data))
|
||||
} catch (error) {
|
||||
console.error('保存数据失败:', error)
|
||||
console.warn('保存数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const getStorage = <T>(key: string): T | null => {
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('读取数据失败:', error)
|
||||
console.warn('读取数据失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const removeStorage = (key: string) => {
|
||||
try {
|
||||
Taro.removeStorageSync(key)
|
||||
} catch (error) {
|
||||
console.error('清除数据失败:', error)
|
||||
console.warn('清除数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ export const clearAllStorage = () => {
|
||||
try {
|
||||
Taro.clearStorageSync()
|
||||
} catch (error) {
|
||||
console.error('清除所有数据失败:', error)
|
||||
console.warn('清除所有数据失败:', error)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import evaluateService, {
|
||||
LastTimeTestResult,
|
||||
} from "@/services/evaluateService";
|
||||
import { useListStore } from "./listStore";
|
||||
import { getBackendErrorMsg } from "@/utils/helper";
|
||||
|
||||
export interface UserState {
|
||||
user: UserInfoType | {};
|
||||
@@ -77,7 +78,7 @@ export const useUser = create<UserState>()((set) => ({
|
||||
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
@@ -87,7 +88,7 @@ export const useUser = create<UserState>()((set) => ({
|
||||
try {
|
||||
(Taro as any).setStorageSync?.(CITY_CACHE_KEY, newArea);
|
||||
} catch (error) {
|
||||
console.error("保存城市缓存失败:", error);
|
||||
console.warn("保存城市缓存失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -122,7 +123,11 @@ export const useUser = create<UserState>()((set) => ({
|
||||
// 只有在更新头像等需要服务器返回新URL的字段时才需要重新获取
|
||||
// 如果需要确保数据一致性,可以在特定场景下手动调用 fetchUserInfo
|
||||
} catch (error) {
|
||||
console.error("更新用户信息失败:", error);
|
||||
console.warn("更新用户信息失败:", error);
|
||||
Taro.showToast({
|
||||
title: getBackendErrorMsg(error, "保存失败"),
|
||||
icon: "none",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -153,7 +158,7 @@ export const useUser = create<UserState>()((set) => ({
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("检查昵称变更状态失败:", error);
|
||||
console.warn("检查昵称变更状态失败:", error);
|
||||
} finally {
|
||||
isCheckingNicknameStatus = false;
|
||||
}
|
||||
@@ -167,7 +172,7 @@ export const useUser = create<UserState>()((set) => ({
|
||||
user: { ...state.user, nickname },
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("更新用户昵称失败:", error);
|
||||
console.warn("更新用户昵称失败:", error);
|
||||
}
|
||||
},
|
||||
// NTRP 测试结果缓存
|
||||
@@ -201,7 +206,7 @@ export const useUser = create<UserState>()((set) => ({
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("获取NTRP测试结果失败:", error);
|
||||
console.warn("获取NTRP测试结果失败:", error);
|
||||
return null;
|
||||
} finally {
|
||||
isFetchingLastTestResult = false;
|
||||
|
||||
@@ -25,7 +25,7 @@ const TestPage: React.FC = () => {
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.error('订阅失败:', err);
|
||||
console.warn('订阅失败:', err);
|
||||
Taro.showToast({
|
||||
title: '订阅失败',
|
||||
icon: 'error',
|
||||
|
||||
@@ -206,11 +206,11 @@ const DownloadBill: React.FC = () => {
|
||||
}
|
||||
},
|
||||
fail: function (err) {
|
||||
console.error("文件下载失败:", err);
|
||||
console.warn("文件下载失败:", err);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.warn(error);
|
||||
}
|
||||
};
|
||||
const handleDownloadBill = async () => {
|
||||
@@ -240,11 +240,11 @@ const DownloadBill: React.FC = () => {
|
||||
}
|
||||
},
|
||||
fail: function (err) {
|
||||
console.error("文件下载失败:", err);
|
||||
console.warn("文件下载失败:", err);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.warn(error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -82,15 +82,15 @@ const DownloadBillRecords: React.FC = () => {
|
||||
console.log('打开文档成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开文档失败', err);
|
||||
console.warn('打开文档失败', err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('下载失败,状态码:', res.statusCode);
|
||||
console.warn('下载失败,状态码:', res.statusCode);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('下载失败', err);
|
||||
console.warn('下载失败', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@/store/pickerOptionsStore";
|
||||
import { handleCustomerService } from "@/services/userService";
|
||||
import evaluateService from "@/services/evaluateService";
|
||||
import { getBackendErrorMsg } from "@/utils/helper";
|
||||
|
||||
const EditProfilePage: React.FC = () => {
|
||||
const { updateUserInfo, updateNickname } = useUserActions();
|
||||
@@ -142,7 +143,7 @@ const EditProfilePage: React.FC = () => {
|
||||
// city: user_data.city || "",
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("加载用户信息失败:", error);
|
||||
// console.warn("加载用户信息失败:", error);
|
||||
// Taro.showToast({
|
||||
// title: "加载用户信息失败",
|
||||
// icon: "error",
|
||||
@@ -169,9 +170,9 @@ const EditProfilePage: React.FC = () => {
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("头像上传失败:", error);
|
||||
console.warn("头像上传失败:", error);
|
||||
Taro.showToast({
|
||||
title: "头像上传失败",
|
||||
title: error.message,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
@@ -246,10 +247,10 @@ const EditProfilePage: React.FC = () => {
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("保存失败:", error);
|
||||
console.warn("保存失败:", error);
|
||||
Taro.showToast({
|
||||
title: "保存失败",
|
||||
icon: "error",
|
||||
title: getBackendErrorMsg(error, "保存失败"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -307,10 +308,10 @@ const EditProfilePage: React.FC = () => {
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("保存失败:", error);
|
||||
console.warn("保存失败:", error);
|
||||
Taro.showToast({
|
||||
title: "保存失败",
|
||||
icon: "error",
|
||||
title: getBackendErrorMsg(error, "保存失败"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -438,7 +439,7 @@ const EditProfilePage: React.FC = () => {
|
||||
const phone = await UserService.parse_phone(e.detail.code);
|
||||
handle_field_edit("phone", phone);
|
||||
} catch (e) {
|
||||
console.error("解析手机号失败:", e);
|
||||
console.warn("解析手机号失败:", e);
|
||||
Taro.showToast({
|
||||
title: "解析手机号失败,请重试",
|
||||
icon: "none",
|
||||
@@ -564,9 +565,8 @@ const EditProfilePage: React.FC = () => {
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.gender ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_value ${form_data.gender ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
{convert_db_gender_to_display(form_data.gender)}
|
||||
</Text>
|
||||
@@ -597,9 +597,8 @@ const EditProfilePage: React.FC = () => {
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.birthday ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_value ${form_data.birthday ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
{form_data.birthday || "选择生日"}
|
||||
</Text>
|
||||
@@ -628,9 +627,8 @@ const EditProfilePage: React.FC = () => {
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.personal_profile ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_value ${form_data.personal_profile ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
{form_data.personal_profile.replace(/\n/g, " ") ||
|
||||
"介绍一下自己"}
|
||||
@@ -661,17 +659,16 @@ const EditProfilePage: React.FC = () => {
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.province ||
|
||||
className={`item_value ${form_data.province ||
|
||||
form_data.city ||
|
||||
form_data.district
|
||||
? ""
|
||||
: "placehoder"
|
||||
}`}
|
||||
? ""
|
||||
: "placehoder"
|
||||
}`}
|
||||
>
|
||||
{form_data.province ||
|
||||
form_data.city ||
|
||||
form_data.district
|
||||
form_data.city ||
|
||||
form_data.district
|
||||
? `${form_data.province} ${form_data.city} ${form_data.district}`
|
||||
: "选择所在地区"}
|
||||
</Text>
|
||||
@@ -697,9 +694,8 @@ const EditProfilePage: React.FC = () => {
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.ntrp_level ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_value ${form_data.ntrp_level ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
{form_data.ntrp_level || "测测你的 NTRP 水平"}
|
||||
</Text>
|
||||
@@ -724,14 +720,12 @@ const EditProfilePage: React.FC = () => {
|
||||
<Text className="item_label">职业</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`item_right ${
|
||||
form_data.occupation ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_right ${form_data.occupation ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`item_value ${
|
||||
form_data.occupation ? "" : "placeholder"
|
||||
}`}
|
||||
className={`item_value ${form_data.occupation ? "" : "placeholder"
|
||||
}`}
|
||||
>
|
||||
{form_data.occupation || "填写你的职业"}
|
||||
</Text>
|
||||
@@ -771,9 +765,9 @@ const EditProfilePage: React.FC = () => {
|
||||
>
|
||||
{form_data.phone
|
||||
? form_data.phone.replace(
|
||||
/(\d{3})(\d{4})(\d{4})/,
|
||||
"$1 $2 $3"
|
||||
)
|
||||
/(\d{3})(\d{4})(\d{4})/,
|
||||
"$1 $2 $3"
|
||||
)
|
||||
: "未绑定"}
|
||||
</Button>
|
||||
<Image
|
||||
|
||||
@@ -85,7 +85,7 @@ const FollowPage: React.FC = () => {
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`加载${TAB_CONFIG.find(t => t.key === tab)?.label}列表失败:`, error);
|
||||
console.warn(`加载${TAB_CONFIG.find(t => t.key === tab)?.label}列表失败:`, error);
|
||||
Taro.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
@@ -163,7 +163,7 @@ const FollowPage: React.FC = () => {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('关注操作失败:', error);
|
||||
console.warn('关注操作失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
@@ -202,7 +202,7 @@ const FollowPage: React.FC = () => {
|
||||
try {
|
||||
load_user_list(default_tab, true);
|
||||
} catch (error) {
|
||||
console.error('初始化加载失败:', error);
|
||||
console.warn('初始化加载失败:', error);
|
||||
Taro.showToast({
|
||||
title: '初始化失败',
|
||||
icon: 'none'
|
||||
@@ -243,7 +243,7 @@ const FollowPage: React.FC = () => {
|
||||
// icon: 'success'
|
||||
// });
|
||||
} catch (error) {
|
||||
console.error('取消关注失败:', error);
|
||||
console.warn('取消关注失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
|
||||
@@ -62,7 +62,7 @@ const MyselfPage: React.FC = () => {
|
||||
// // }
|
||||
// // set_game_records(games_data);
|
||||
// } catch (error) {
|
||||
// console.error("加载用户数据失败:", error);
|
||||
// console.warn("加载用户数据失败:", error);
|
||||
// Taro.showToast({
|
||||
// title: "加载失败,请重试",
|
||||
// icon: "error",
|
||||
@@ -89,11 +89,10 @@ const MyselfPage: React.FC = () => {
|
||||
await fetchUserInfo();
|
||||
}
|
||||
|
||||
|
||||
// 获取测试结果
|
||||
const res = await evaluateService.getLastResult();
|
||||
if (res.code === 0) {
|
||||
console.log( "getLastResult", res.data);
|
||||
console.log("getLastResult", res.data);
|
||||
setHasTestInLastMonth(res.data.has_test_in_last_month);
|
||||
}
|
||||
};
|
||||
@@ -110,25 +109,28 @@ const MyselfPage: React.FC = () => {
|
||||
});
|
||||
|
||||
// 分类球局数据(使用 useCallback 包装,避免每次渲染都创建新函数)
|
||||
const classifyGameRecords = useCallback((
|
||||
game_records: TennisMatch[]
|
||||
): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => {
|
||||
const now = new Date().getTime();
|
||||
return game_records.reduce(
|
||||
(result, cur) => {
|
||||
let { end_time } = cur;
|
||||
end_time = end_time.replace(/\s/, "T");
|
||||
new Date(end_time).getTime() > now
|
||||
? result.notEndGames.push(cur)
|
||||
: result.finishedGames.push(cur);
|
||||
return result;
|
||||
},
|
||||
{
|
||||
notEndGames: [] as TennisMatch[],
|
||||
finishedGames: [] as TennisMatch[],
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
const classifyGameRecords = useCallback(
|
||||
(
|
||||
game_records: TennisMatch[]
|
||||
): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => {
|
||||
const now = new Date().getTime();
|
||||
return game_records.reduce(
|
||||
(result, cur) => {
|
||||
let { end_time } = cur;
|
||||
end_time = end_time.replace(/\s/, "T");
|
||||
new Date(end_time).getTime() > now
|
||||
? result.notEndGames.push(cur)
|
||||
: result.finishedGames.unshift(cur);
|
||||
return result;
|
||||
},
|
||||
{
|
||||
notEndGames: [] as TennisMatch[],
|
||||
finishedGames: [] as TennisMatch[],
|
||||
}
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// 加载球局数据(使用 useCallback 包装,避免每次渲染都创建新函数)
|
||||
const load_game_data = useCallback(async () => {
|
||||
@@ -150,7 +152,7 @@ const MyselfPage: React.FC = () => {
|
||||
setEndedGameRecords(finishedGames);
|
||||
// set_game_records(games_data);
|
||||
} catch (error) {
|
||||
console.error("加载球局数据失败:", error);
|
||||
console.warn("加载球局数据失败:", error);
|
||||
}
|
||||
}, [active_tab, user_info, classifyGameRecords]);
|
||||
|
||||
@@ -176,7 +178,7 @@ const MyselfPage: React.FC = () => {
|
||||
duration: 1500,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("关注操作失败:", error);
|
||||
console.warn("关注操作失败:", error);
|
||||
Taro.showToast({
|
||||
title: "操作失败,请重试",
|
||||
icon: "error",
|
||||
@@ -272,7 +274,7 @@ const MyselfPage: React.FC = () => {
|
||||
</View>
|
||||
|
||||
{/* 球局列表 */}
|
||||
<View className="game_list_section" >
|
||||
<View className="game_list_section">
|
||||
<ScrollView scrollY refresherBackground="#FAFAFA">
|
||||
<ListContainer
|
||||
data={game_records}
|
||||
|
||||
@@ -117,7 +117,7 @@ const OtherUserPage: React.FC = () => {
|
||||
});
|
||||
setIsFollowing(userData.is_following || false);
|
||||
} catch (error) {
|
||||
console.error("加载用户数据失败:", error);
|
||||
console.warn("加载用户数据失败:", error);
|
||||
Taro.showToast({
|
||||
title: "加载失败",
|
||||
icon: "none",
|
||||
@@ -141,7 +141,7 @@ const OtherUserPage: React.FC = () => {
|
||||
end_time = end_time.replace(/\s/, "T");
|
||||
new Date(end_time).getTime() > now
|
||||
? result.notEndGames.push(cur)
|
||||
: result.finishedGames.push(cur);
|
||||
: result.finishedGames.unshift(cur);
|
||||
return result;
|
||||
},
|
||||
{
|
||||
@@ -169,7 +169,7 @@ const OtherUserPage: React.FC = () => {
|
||||
setGameRecords(notEndGames);
|
||||
setEndedGameRecords(finishedGames);
|
||||
} catch (error) {
|
||||
console.error("加载球局数据失败:", error);
|
||||
console.warn("加载球局数据失败:", error);
|
||||
Taro.showToast({
|
||||
title: "加载失败,请重试",
|
||||
icon: "error",
|
||||
@@ -200,7 +200,7 @@ const OtherUserPage: React.FC = () => {
|
||||
duration: 1500,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("关注操作失败:", error);
|
||||
console.warn("关注操作失败:", error);
|
||||
Taro.showToast({
|
||||
title: "操作失败",
|
||||
icon: "none",
|
||||
@@ -235,7 +235,7 @@ const OtherUserPage: React.FC = () => {
|
||||
try {
|
||||
await Promise.all([load_user_data(), load_game_data()]);
|
||||
} catch (error) {
|
||||
console.error("刷新失败:", error);
|
||||
console.warn("刷新失败:", error);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ const QueryTransactions = () => {
|
||||
setSearchHistory(response.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
background-color: #f5f5f5;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 0 20px;
|
||||
.custom-navbar {
|
||||
height: 56px;
|
||||
/* 通常与原生导航栏高度一致 */
|
||||
@@ -17,6 +18,7 @@
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.detail-navigator {
|
||||
@@ -48,18 +50,22 @@
|
||||
}
|
||||
|
||||
.form-item {
|
||||
padding: 20px;
|
||||
padding: 16px 0;
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #0000000d;
|
||||
font-size: 14px;
|
||||
|
||||
.form-label {
|
||||
width: 56px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +78,6 @@
|
||||
letter-spacing: 0px;
|
||||
vertical-align: middle;
|
||||
color: #3c3c4366;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -106,6 +111,5 @@
|
||||
border-radius: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,12 @@ const SetTransactionPassword: React.FC = () => {
|
||||
const { new_password, confirm_password, sms_code } = formData;
|
||||
if (handleType === "set") {
|
||||
setValid(
|
||||
(sms_code !== "") && (new_password.length === 6) && (confirm_password.length === 6)
|
||||
sms_code !== "" &&
|
||||
new_password.length === 6 &&
|
||||
confirm_password.length === 6
|
||||
);
|
||||
} else {
|
||||
setValid((new_password.length === 6) && (confirm_password.length === 6));
|
||||
setValid(new_password.length === 6 && confirm_password.length === 6);
|
||||
}
|
||||
}, [formData]);
|
||||
|
||||
@@ -97,7 +99,7 @@ const SetTransactionPassword: React.FC = () => {
|
||||
icon: "success",
|
||||
});
|
||||
let delta = handleType === "set" ? 1 : 2;
|
||||
Taro.navigateBack({ delta })
|
||||
Taro.navigateBack({ delta });
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: "设置交易密码失败",
|
||||
@@ -166,13 +168,11 @@ const SetTransactionPassword: React.FC = () => {
|
||||
Taro.navigateBack();
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
className="form-item"
|
||||
style={{ marginTop: `${totalHeight}px` }}
|
||||
>
|
||||
<View className="form-item" style={{ marginTop: `${totalHeight}px` }}>
|
||||
<Text className="form-label">交易密码</Text>
|
||||
<Input
|
||||
placeholder="请输入交易密码"
|
||||
placeholderStyle="color: #d9d9d9;"
|
||||
password
|
||||
type="number"
|
||||
maxlength={6}
|
||||
@@ -185,6 +185,7 @@ const SetTransactionPassword: React.FC = () => {
|
||||
<Text className="form-label">重复密码</Text>
|
||||
<Input
|
||||
placeholder="请再次输入交易密码"
|
||||
placeholderStyle="color: #d9d9d9;"
|
||||
password
|
||||
type="number"
|
||||
maxlength={6}
|
||||
@@ -198,6 +199,7 @@ const SetTransactionPassword: React.FC = () => {
|
||||
<Text className="form-label">手机验证</Text>
|
||||
<Input
|
||||
placeholder="请输入验证码"
|
||||
placeholderStyle="color: #d9d9d9;"
|
||||
type="number"
|
||||
onInput={(e) => {
|
||||
handleInput(e, "sms_code");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
overflow-y: hidden;
|
||||
padding: 0 20px;
|
||||
.custom-navbar {
|
||||
height: 56px;
|
||||
/* 通常与原生导航栏高度一致 */
|
||||
@@ -48,18 +49,22 @@
|
||||
}
|
||||
|
||||
.form-item {
|
||||
padding: 20px;
|
||||
padding: 16px 0;
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #0000000d;
|
||||
font-size: 14px;
|
||||
|
||||
.form-label {
|
||||
width: 56px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +100,5 @@
|
||||
border-radius: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +139,7 @@ const ValidPhone: React.FC = () => {
|
||||
Taro.navigateBack();
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
className="form-item"
|
||||
style={{ marginTop: `${totalHeight}px` }}
|
||||
>
|
||||
<View className="form-item" style={{ marginTop: `${totalHeight}px` }}>
|
||||
<Text className="form-label">手机号</Text>
|
||||
<Input defaultValue={formData.phone} type="number" disabled></Input>
|
||||
</View>
|
||||
@@ -150,6 +147,7 @@ const ValidPhone: React.FC = () => {
|
||||
<Text className="form-label">验证码</Text>
|
||||
<Input
|
||||
placeholder="请输入验证码"
|
||||
placeholderStyle="color: #d9d9d9;"
|
||||
type="number"
|
||||
onInput={(e) => {
|
||||
handleInput(e, "sms_code");
|
||||
|
||||
@@ -195,7 +195,7 @@ const WalletPage: React.FC = () => {
|
||||
const res = await httpService.post("/wallet/check_password_status");
|
||||
set_password_status(res.data.is_password_set);
|
||||
} catch (e) {
|
||||
console.error("检查交易密码状态失败:", e);
|
||||
console.warn("检查交易密码状态失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -218,7 +218,7 @@ const WalletPage: React.FC = () => {
|
||||
total_withdraw,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("加载钱包数据失败:", error);
|
||||
console.warn("加载钱包数据失败:", error);
|
||||
|
||||
let errorMessage = "加载失败,请重试";
|
||||
if (
|
||||
@@ -262,7 +262,7 @@ const WalletPage: React.FC = () => {
|
||||
set_transactions([]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("加载交易记录失败:", error);
|
||||
console.warn("加载交易记录失败:", error);
|
||||
set_transactions([]);
|
||||
|
||||
let errorMessage = "加载交易记录失败";
|
||||
@@ -500,6 +500,7 @@ const WalletPage: React.FC = () => {
|
||||
{/* 顶部导航栏 */}
|
||||
<GeneralNavbar
|
||||
title={pageTitle}
|
||||
backgroundColor="#ffffff"
|
||||
showBack={true}
|
||||
showAvatar={false}
|
||||
onBack={() => {
|
||||
|
||||
@@ -49,7 +49,7 @@ const Withdrawal: React.FC = () => {
|
||||
}, [initializeKeyboardListener, addListener]);
|
||||
const [showTips, setShowTips] = useState(false);
|
||||
const [tipsText, setTipsText] = useState<string>("");
|
||||
const [inputValue, setInputValue] = useState<string>("0.00");
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [walletInfo, setWalletInfo] = useState<WalletInfo>({
|
||||
balance: "0.00",
|
||||
});
|
||||
@@ -65,8 +65,8 @@ const Withdrawal: React.FC = () => {
|
||||
|
||||
const [inputValueObj, setInputValueObj] = useState({
|
||||
integer: "0",
|
||||
decimal: "00"
|
||||
})
|
||||
decimal: "00",
|
||||
});
|
||||
|
||||
useDidShow(() => {
|
||||
load_wallet_data();
|
||||
@@ -87,11 +87,11 @@ const Withdrawal: React.FC = () => {
|
||||
}, [show_withdraw_popup]);
|
||||
|
||||
useEffect(() => {
|
||||
const value = Number(inputValue).toFixed(2).split(".")
|
||||
const integer = value[0]
|
||||
const decimal = value[1]
|
||||
setInputValueObj({ integer, decimal })
|
||||
}, [inputValue])
|
||||
const value = Number(inputValue).toFixed(2).split(".");
|
||||
const integer = value[0];
|
||||
const decimal = value[1];
|
||||
setInputValueObj({ integer, decimal });
|
||||
}, [inputValue]);
|
||||
|
||||
const validateWithdrawAmount = (amount: string) => {
|
||||
if (Number(amount) > Number(walletInfo.balance)) {
|
||||
@@ -135,7 +135,7 @@ const Withdrawal: React.FC = () => {
|
||||
total_withdraw,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("加载钱包数据失败:", error);
|
||||
console.warn("加载钱包数据失败:", error);
|
||||
|
||||
let errorMessage = "加载失败,请重试";
|
||||
if (
|
||||
@@ -171,7 +171,7 @@ const Withdrawal: React.FC = () => {
|
||||
setMapErrorCodes(mapErrorCodes);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("获取提现错误码失败:", error);
|
||||
console.warn("获取提现错误码失败:", error);
|
||||
}
|
||||
};
|
||||
const handleWithdraw = async () => {
|
||||
@@ -307,11 +307,9 @@ const Withdrawal: React.FC = () => {
|
||||
showBack={true}
|
||||
showAvatar={false}
|
||||
onBack={() => {
|
||||
const pages = Taro.getCurrentPages()
|
||||
const prevPage = pages[pages.length - 2]
|
||||
prevPage.setData({
|
||||
updateList: withdrawSuccess
|
||||
})
|
||||
const pages = Taro.getCurrentPages();
|
||||
const prevPage = pages[pages.length - 2];
|
||||
prevPage.setData({ updateList: true });
|
||||
Taro.navigateBack();
|
||||
}}
|
||||
/>
|
||||
@@ -325,6 +323,7 @@ const Withdrawal: React.FC = () => {
|
||||
<Input
|
||||
type="digit"
|
||||
placeholder="0.00"
|
||||
placeholderStyle="color:rgba(120, 120, 128, 0.12);"
|
||||
cursorColor="#000"
|
||||
value={inputValue}
|
||||
onInput={handleInput}
|
||||
|
||||
@@ -33,7 +33,7 @@ const initUserAuthCore = async (): Promise<void> => {
|
||||
await fetchUserInfo();
|
||||
await checkNicknameChangeStatus();
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
console.warn("获取用户信息失败:", error);
|
||||
}
|
||||
} else {
|
||||
// 未登录,尝试静默登录
|
||||
@@ -45,12 +45,12 @@ const initUserAuthCore = async (): Promise<void> => {
|
||||
await checkNicknameChangeStatus();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("静默登录失败:", error);
|
||||
console.warn("静默登录失败:", error);
|
||||
// 静默登录失败不影响使用
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("初始化用户授权失败:", error);
|
||||
console.warn("初始化用户授权失败:", error);
|
||||
} finally {
|
||||
isInitializingAuth = false;
|
||||
authInitPromise = null;
|
||||
|
||||
@@ -7,6 +7,12 @@ const dateIcon = `${OSS_BASE}/front/ball/images/1b49476e-0eda-42ff-b08c-002ce510
|
||||
const mapIcon = `${OSS_BASE}/front/ball/images/06b994fa-9227-4708-8555-8a07af8d0c3b.jpg`;
|
||||
const logoText = `${OSS_BASE}/system/youchang_tip_text.png`;
|
||||
|
||||
/** 给图片 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)}`;
|
||||
}
|
||||
|
||||
export function base64ToTempFilePath(base64Data: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fsm = Taro.getFileSystemManager();
|
||||
@@ -55,10 +61,28 @@ function getImageWh(src: string): Promise<{ width: number; height: number }> {
|
||||
}
|
||||
|
||||
/** 加载图片 */
|
||||
function loadImage(canvas: any, src: string): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
function loadImage(canvas: any, src: string, key?: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
const img = canvas.createImage();
|
||||
img.onload = () => resolve(img);
|
||||
|
||||
img.crossOrigin = "anonymous";
|
||||
|
||||
img.onload = () => {
|
||||
clearTimeout(timer);
|
||||
resolve(img);
|
||||
};
|
||||
img.onerror = (e: Error) => {
|
||||
clearTimeout(timer);
|
||||
const errMsg = `Image load failed: ${key}: ${src}`
|
||||
console.warn(errMsg)
|
||||
reject(new Error(errMsg));
|
||||
};
|
||||
|
||||
timer = setTimeout(() => {
|
||||
reject(new Error(`Image load timeout: ${key}: ${src}`));
|
||||
}, 8000);
|
||||
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
@@ -146,6 +170,7 @@ async function drawRotateCoverImage(
|
||||
rotate = 0 // 旋转角度(弧度)
|
||||
) {
|
||||
const { width, height } = await getImageWh(src);
|
||||
console.log('width', width, 'height', height)
|
||||
const scale = Math.max(w / width, h / height);
|
||||
const newW = width * scale;
|
||||
const newH = height * scale;
|
||||
@@ -179,6 +204,7 @@ async function drawRotateCoverImage(
|
||||
|
||||
// 绘制 cover
|
||||
ctx.drawImage(img, offsetX, offsetY, newW, newH);
|
||||
console.log('drawImage', offsetX, offsetY, newW, newH)
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -290,6 +316,8 @@ function drawTextWrap(
|
||||
|
||||
/** 核心纯函数:生成海报图片 */
|
||||
export async function generatePosterImage(data: any): Promise<string> {
|
||||
|
||||
|
||||
console.log("start !!!!");
|
||||
// const dpr = Taro.getWindowInfo().pixelRatio;
|
||||
const dpr = 1;
|
||||
@@ -297,19 +325,30 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
const width = 600;
|
||||
const height = 1000;
|
||||
|
||||
console.log('width', width, 'height', height)
|
||||
const canvas = Taro.createOffscreenCanvas({ type: "2d", width: width * dpr, height: height * dpr });
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
|
||||
console.log('ctx', ctx)
|
||||
|
||||
|
||||
// 背景渐变
|
||||
roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC");
|
||||
|
||||
const bgImg = await loadImage(canvas, bgUrl);
|
||||
console.log('bgUrl', bgUrl)
|
||||
const bgImg = await loadImage(canvas, with_cache_bust(bgUrl), 'bgUrl');
|
||||
ctx.drawImage(bgImg, 0, 0, width, height);
|
||||
console.log('bgUrlend',)
|
||||
|
||||
|
||||
roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6));
|
||||
|
||||
|
||||
// 顶部图片
|
||||
const mainImg = await loadImage(canvas, data.mainCoursal);
|
||||
const mainImg = await loadImage(canvas, with_cache_bust(data.mainCoursal), 'mainCoursal');
|
||||
console.log('mainCoursal', data.mainCoursal)
|
||||
await drawRotateCoverImage(
|
||||
ctx,
|
||||
canvas,
|
||||
@@ -342,7 +381,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
left = 20;
|
||||
|
||||
// 用户头像
|
||||
const avatarImg = await loadImage(canvas, data.avatarUrl);
|
||||
const avatarImg = await loadImage(canvas, with_cache_bust(data.avatarUrl), 'avatar');
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(left + 30, top + 30, 30, 0, Math.PI * 2);
|
||||
@@ -363,7 +402,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
ctx.fillStyle = "#00B578";
|
||||
ctx.fillText("球局", left, top);
|
||||
|
||||
const ringImg = await loadImage(canvas, ringUrl);
|
||||
const ringImg = await loadImage(canvas, with_cache_bust(ringUrl), 'ring');
|
||||
ctx.drawImage(ringImg, left - 10, top - 30, 80, 36);
|
||||
|
||||
left = 20;
|
||||
@@ -377,7 +416,9 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
top = r.top + 30;
|
||||
left = 20;
|
||||
|
||||
const dateImg = await loadImage(canvas, dateIcon);
|
||||
const dateImg = await loadImage(canvas, with_cache_bust(dateIcon), 'date');
|
||||
|
||||
console.log('dateIcon', dateIcon)
|
||||
await drawCoverImage(
|
||||
ctx,
|
||||
canvas,
|
||||
@@ -404,7 +445,8 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
left = 20;
|
||||
top += 24;
|
||||
|
||||
const mapImg = await loadImage(canvas, mapIcon);
|
||||
|
||||
const mapImg = await loadImage(canvas, with_cache_bust(mapIcon), 'map');
|
||||
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12);
|
||||
|
||||
left += 40 + 16;
|
||||
@@ -418,7 +460,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
top = r.top + 60;
|
||||
|
||||
const logoWh = await getImageWh(logoText);
|
||||
const logoTextImg = await loadImage(canvas, logoText);
|
||||
const logoTextImg = await loadImage(canvas, with_cache_bust(logoText), 'logo');
|
||||
ctx.drawImage(
|
||||
logoTextImg,
|
||||
left,
|
||||
@@ -428,7 +470,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
400 / (logoWh.width / logoWh.height)
|
||||
);
|
||||
|
||||
const qrImg = await loadImage(canvas, data.qrCodeUrl);
|
||||
const qrImg = await loadImage(canvas, with_cache_bust(data.qrCodeUrl), 'qrcode');
|
||||
|
||||
// roundRectGradient(ctx, width - 12 - 150, height - 22 - 140, 140, 140, 20, "#fff", "#fff")
|
||||
ctx.drawImage(qrImg, width - 22 - 100, height - 22 - 100 - 2, 100, 100);
|
||||
@@ -440,11 +482,13 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
ctx.font = "400 20px sans-serif";
|
||||
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
|
||||
|
||||
console.log('canvas', canvas)
|
||||
// 导出图片
|
||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||
canvas,
|
||||
fileType: 'png',
|
||||
quality: 0.7,
|
||||
});
|
||||
console.log('tempFilePath', tempFilePath)
|
||||
return tempFilePath;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Taro from "@tarojs/taro";
|
||||
import { check_login_status, get_user_info } from "@/services/loginService";
|
||||
import { useUser } from "@/store/userStore";
|
||||
import { Dayjs } from "dayjs";
|
||||
|
||||
// 普通函数,不调用 useLoad
|
||||
export const sceneRedirectLogic = (options, defaultPage: string) => {
|
||||
@@ -23,7 +24,7 @@ export const sceneRedirectLogic = (options, defaultPage: string) => {
|
||||
url: query ? `/${defaultPage}?${query}` : `/${defaultPage}`,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,6 +95,19 @@ export function toast(message) {
|
||||
Taro.showToast({ title: message, icon: "none" });
|
||||
}
|
||||
|
||||
/** 从接口/请求错误中取出后端返回的文案,用于保存失败等场景的 toast */
|
||||
export function getBackendErrorMsg(error: any, fallback = "操作失败"): string {
|
||||
if (error == null) return fallback;
|
||||
const msg =
|
||||
error?.message ||
|
||||
(typeof error?.error === "string" ? error.error : error?.error?.message) ||
|
||||
error?.data?.message ||
|
||||
error?.data?.msg ||
|
||||
"";
|
||||
const s = String(msg).trim();
|
||||
return s || fallback;
|
||||
}
|
||||
|
||||
// 将·作为连接符插入到标签文本之间
|
||||
export function insertDotInTags(tags: string[]) {
|
||||
if (!tags) return [];
|
||||
@@ -122,3 +136,31 @@ export function genNTRPRequirementText(min, max) {
|
||||
export function isPhoneNumber(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))}小时`;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,20 @@ export interface LocationInfo {
|
||||
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> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
;(Taro as any).getLocation({
|
||||
type: 'wgs84',
|
||||
success: (res) => {
|
||||
console.log('===获取地理位置', res)
|
||||
@@ -27,7 +37,7 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
|
||||
resolve({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
address
|
||||
address: normalize_address(address)
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -48,12 +58,12 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
|
||||
// 选择地图位置
|
||||
export const chooseLocation = (): Promise<LocationInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.chooseLocation({
|
||||
;(Taro as any).chooseLocation({
|
||||
success: (res) => {
|
||||
resolve({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
address: res.address,
|
||||
address: normalize_address(res.address),
|
||||
name: res.name
|
||||
})
|
||||
},
|
||||
@@ -66,7 +76,7 @@ export const chooseLocation = (): Promise<LocationInfo> => {
|
||||
|
||||
export const getLocation = (): Promise<Location> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.getLocation({
|
||||
;(Taro as any).getLocation({
|
||||
success: (res) => {
|
||||
resolve({
|
||||
latitude: res.latitude,
|
||||
|
||||
@@ -106,6 +106,12 @@ const drawLabel = (ctx: any, x: number, y: number, width: number, height: number
|
||||
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)
|
||||
const loadImage = (src: string): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -117,7 +123,7 @@ const loadImage = (src: string): Promise<any> => {
|
||||
const img = off.createImage()
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = reject
|
||||
img.src = src
|
||||
img.src = with_cache_bust(src)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
@@ -533,7 +539,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`)
|
||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||
|
||||
try {
|
||||
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
||||
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
||||
@@ -554,7 +559,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
||||
console.log('Canvas绘制命令已发送')
|
||||
|
||||
} catch (error) {
|
||||
console.error('绘制分享卡片失败:', error)
|
||||
console.warn('绘制分享卡片失败:', error)
|
||||
Taro.showToast({
|
||||
title: '生成分享卡片失败',
|
||||
icon: 'none'
|
||||
|
||||
@@ -16,7 +16,7 @@ class TokenManager {
|
||||
try {
|
||||
return Taro.getStorageSync(TOKEN_KEY)
|
||||
} catch (error) {
|
||||
console.error('获取访问令牌失败:', error)
|
||||
console.warn('获取访问令牌失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class TokenManager {
|
||||
try {
|
||||
return Taro.getStorageSync(REFRESH_TOKEN_KEY)
|
||||
} catch (error) {
|
||||
console.error('获取刷新令牌失败:', error)
|
||||
console.warn('获取刷新令牌失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class TokenManager {
|
||||
try {
|
||||
return Taro.getStorageSync(TOKEN_EXPIRES_KEY)
|
||||
} catch (error) {
|
||||
console.error('获取令牌过期时间失败:', error)
|
||||
console.warn('获取令牌过期时间失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class TokenManager {
|
||||
Taro.setStorageSync(TOKEN_EXPIRES_KEY, tokenInfo.expiresAt)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置令牌失败:', error)
|
||||
console.warn('设置令牌失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class TokenManager {
|
||||
Taro.removeStorageSync(REFRESH_TOKEN_KEY)
|
||||
Taro.removeStorageSync(TOKEN_EXPIRES_KEY)
|
||||
} catch (error) {
|
||||
console.error('清除令牌失败:', error)
|
||||
console.warn('清除令牌失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function saveImage(url) {
|
||||
await Taro.saveImageToPhotosAlbum({ filePath })
|
||||
Taro.showToast({ title: "保存成功" })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.warn(e)
|
||||
Taro.showToast({ title: "图片保存失败", icon: "none" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ export interface ListCardProps {
|
||||
venue_image_list: {
|
||||
url: string;
|
||||
}[];
|
||||
venue_description: string;
|
||||
location_name: string;
|
||||
game_type: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user