Compare commits
10 Commits
0f8dd44f5a
...
feat/juguo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c8026f61 | ||
| 05966b2acb | |||
|
|
4cf2b959b5 | ||
|
|
43610dcf99 | ||
|
|
05aa820466 | ||
|
|
b154e31f8f | ||
|
|
669ee2fe4e | ||
|
|
281ee2b746 | ||
|
|
132c74d27c | ||
|
|
6b6a4c9480 |
@@ -4,7 +4,11 @@ export default {
|
|||||||
quiet: false,
|
quiet: false,
|
||||||
stats: true
|
stats: true
|
||||||
},
|
},
|
||||||
mini: {},
|
mini: {
|
||||||
|
webpackChain(chain) {
|
||||||
|
chain.devtool('source-map')
|
||||||
|
}
|
||||||
|
},
|
||||||
h5: {},
|
h5: {},
|
||||||
// 添加这个配置来显示完整错误信息
|
// 添加这个配置来显示完整错误信息
|
||||||
compiler: {
|
compiler: {
|
||||||
|
|||||||
@@ -48,7 +48,14 @@ const CustomPopup: React.FC<CustomPopupProps> = ({
|
|||||||
const touchStartY = useRef(0)
|
const touchStartY = useRef(0)
|
||||||
|
|
||||||
// 使用全局键盘状态
|
// 使用全局键盘状态
|
||||||
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight()
|
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardHeight()
|
||||||
|
|
||||||
|
// 当弹窗显示时,设置键盘为不可见
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setKeyboardVisible(false)
|
||||||
|
}
|
||||||
|
}, [visible, setKeyboardVisible])
|
||||||
|
|
||||||
// 使用全局键盘状态监听
|
// 使用全局键盘状态监听
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const GamePlayType = (props: IProps) => {
|
|||||||
const { name, onChange, value, options } = props;
|
const { name, onChange, value, options } = props;
|
||||||
return (
|
return (
|
||||||
<View className={styles.gamePlayWrapper}>
|
<View className={styles.gamePlayWrapper}>
|
||||||
<TitleComponent title="玩法" icon={<Image src={img.ICON_SITE} />} />
|
<TitleComponent title="玩法" icon={<Image src={img.ICON_GAME_PLAY} />} />
|
||||||
<Bubble
|
<Bubble
|
||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const PopupPicker = ({
|
|||||||
ntrpTested,
|
ntrpTested,
|
||||||
}: PickerProps) => {
|
}: PickerProps) => {
|
||||||
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
|
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
|
||||||
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([]);
|
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([...options]);
|
||||||
const [pickerCurrentValue, setPickerCurrentValue] =
|
const [pickerCurrentValue, setPickerCurrentValue] =
|
||||||
useState<(string | number)[]>(value);
|
useState<(string | number)[]>(value);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
useUserActions,
|
useUserActions,
|
||||||
useNicknameChangeStatus,
|
useNicknameChangeStatus,
|
||||||
useLastTestResult,
|
useLastTestResult,
|
||||||
|
useUserInfo,
|
||||||
} from "@/store/userStore";
|
} from "@/store/userStore";
|
||||||
import { UserInfoType } from "@/services/userService";
|
import { UserInfoType } from "@/services/userService";
|
||||||
import {
|
import {
|
||||||
@@ -73,7 +74,6 @@ const on_edit = () => {
|
|||||||
// 用户信息卡片组件
|
// 用户信息卡片组件
|
||||||
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||||
editable = true,
|
editable = true,
|
||||||
user_info,
|
|
||||||
is_current_user,
|
is_current_user,
|
||||||
is_following = false,
|
is_following = false,
|
||||||
collapseProfile,
|
collapseProfile,
|
||||||
@@ -84,6 +84,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
set_user_info,
|
set_user_info,
|
||||||
onTab,
|
onTab,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const user_info = useUserInfo();
|
||||||
const nickname_change_status = useNicknameChangeStatus();
|
const nickname_change_status = useNicknameChangeStatus();
|
||||||
const { setShowGuideBar } = useGlobalState();
|
const { setShowGuideBar } = useGlobalState();
|
||||||
const { updateUserInfo, updateNickname, fetchLastTestResult } =
|
const { updateUserInfo, updateNickname, fetchLastTestResult } =
|
||||||
@@ -122,11 +124,15 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
// 表单状态
|
// 表单状态
|
||||||
const [form_data, set_form_data] = useState<Partial<UserInfoType>>({});
|
const [form_data, set_form_data] = useState<Partial<UserInfoType>>({ ...user_info });
|
||||||
|
|
||||||
useDidShow(() => {
|
// useDidShow(() => {
|
||||||
|
// set_form_data({ ...user_info });
|
||||||
|
// });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
set_form_data({ ...user_info });
|
set_form_data({ ...user_info });
|
||||||
});
|
}, [user_info])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const visibles = [
|
const visibles = [
|
||||||
@@ -372,7 +378,6 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
urls: [url],
|
urls: [url],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="user_info_card">
|
<View className="user_info_card">
|
||||||
{/* 头像和基本信息 */}
|
{/* 头像和基本信息 */}
|
||||||
@@ -650,16 +655,16 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
<PopupPicker
|
<PopupPicker
|
||||||
showHeader={true}
|
showHeader={true}
|
||||||
title="选择性别"
|
title="选择性别"
|
||||||
options={[
|
options={
|
||||||
[
|
[
|
||||||
{ text: "男", value: "0" },
|
{ text: "男", value: "0" },
|
||||||
{ text: "女", value: "1" },
|
{ text: "女", value: "1" },
|
||||||
{ text: "保密", value: "2" },
|
{ text: "保密", value: "2" },
|
||||||
],
|
]
|
||||||
]}
|
}
|
||||||
visible={gender_picker_visible}
|
visible={gender_picker_visible}
|
||||||
setvisible={setGenderPickerVisible}
|
setvisible={setGenderPickerVisible}
|
||||||
value={form_data.gender === "" ? ["0"] : [form_data.gender]}
|
value={!form_data.gender ? ["0"] : [form_data.gender]}
|
||||||
onChange={handle_gender_change}
|
onChange={handle_gender_change}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -868,8 +873,7 @@ export const GameTabs: React.FC<GameTabsProps> = ({
|
|||||||
<Text className="tab_text">{hosted_text}</Text>
|
<Text className="tab_text">{hosted_text}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`tab_item ${
|
className={`tab_item ${active_tab === "participated" ? "active" : ""
|
||||||
active_tab === "participated" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => on_tab_change("participated")}
|
onClick={() => on_tab_change("participated")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -72,4 +72,5 @@ export default {
|
|||||||
ICON_LIST_CHANGDA: require("@/static/list/icon-changda.svg"),
|
ICON_LIST_CHANGDA: require("@/static/list/icon-changda.svg"),
|
||||||
ICON_LIST_CHANGDA_QIuju: require("@/static/list/changdaqiuju.png"),
|
ICON_LIST_CHANGDA_QIuju: require("@/static/list/changdaqiuju.png"),
|
||||||
ICON_RELOCATE: require("@/static/list/icon-relocate.svg"),
|
ICON_RELOCATE: require("@/static/list/icon-relocate.svg"),
|
||||||
|
ICON_GAME_PLAY: require("@/static/list/icon_game_type.svg"),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -132,40 +132,39 @@ const ListContainer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 插入 banner 卡片
|
// showNumber 为 0 表示尚未同步,不参与截断;截断时只限制「数据条数」,插卡不占数据条数
|
||||||
|
const shouldLimitByShowNumber = showNumber > 0;
|
||||||
|
|
||||||
|
// 插入 banner 卡片(在 bannerListIndex 位置插入,不替换数据)
|
||||||
function insertBannerCard(list) {
|
function insertBannerCard(list) {
|
||||||
if (!bannerListImage) return list;
|
if (!bannerListImage) return list;
|
||||||
if (!list || !Array.isArray(list)) return list ?? [];
|
if (!list || !Array.isArray(list)) {
|
||||||
|
list = [];
|
||||||
|
}
|
||||||
|
const idx = Number(bannerListIndex);
|
||||||
return [
|
return [
|
||||||
...list.slice(0, Number(bannerListIndex)),
|
...list.slice(0, idx),
|
||||||
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
||||||
...list.slice(Number(bannerListIndex))
|
...list.slice(idx),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
// 对于没有 ntrp 等级的用户每个月展示一次,插在第 2 条数据后面;插卡是插入不替换,保留全部 showNumber 条数据
|
||||||
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
|
|
||||||
function insertEvaluateCard(list) {
|
function insertEvaluateCard(list) {
|
||||||
let result: any[];
|
if (!list || !Array.isArray(list)) return insertBannerCard(list ?? []);
|
||||||
|
|
||||||
if (!evaluateFlag) {
|
const limitedList = shouldLimitByShowNumber ? list.slice(0, showNumber) : list;
|
||||||
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
||||||
} else if (!list || list.length === 0) {
|
if (!evaluateFlag || hasTestInLastMonth) {
|
||||||
result = list;
|
return insertBannerCard(limitedList);
|
||||||
} else if (hasTestInLastMonth) {
|
|
||||||
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
||||||
} else if (list.length <= 2) {
|
|
||||||
result = [...list, { type: "evaluateCard" }];
|
|
||||||
} else {
|
|
||||||
const [item1, item2, ...rest] = list;
|
|
||||||
result = [
|
|
||||||
item1,
|
|
||||||
item2,
|
|
||||||
{ type: "evaluateCard" },
|
|
||||||
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (limitedList.length <= 2) {
|
||||||
|
return insertBannerCard([...limitedList, { type: "evaluateCard" }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item1, item2, ...rest] = limitedList;
|
||||||
|
const result = [item1, item2, { type: "evaluateCard" }, ...rest];
|
||||||
return insertBannerCard(result);
|
return insertBannerCard(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,10 +203,12 @@ const ListContainer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showNoData = isShowNoData && !loading && memoizedList?.length === 0;
|
||||||
|
|
||||||
// 渲染列表
|
// 渲染列表
|
||||||
const renderList = () => {
|
const renderList = () => {
|
||||||
// 请求数据为空
|
// 请求数据为空
|
||||||
if (isShowNoData) {
|
if (showNoData) {
|
||||||
return (
|
return (
|
||||||
<ListLoadError
|
<ListLoadError
|
||||||
reload={reload}
|
reload={reload}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
const qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
const qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||||
await delay(100);
|
await delay(100);
|
||||||
// Taro.showLoading({ title: "生成中..." });
|
// Taro.showLoading({ title: "生成中..." });
|
||||||
|
console.log('url', qrCodeUrl)
|
||||||
const url = await generatePosterImage({
|
const url = await generatePosterImage({
|
||||||
playType: play_type,
|
playType: play_type,
|
||||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||||
@@ -160,6 +161,8 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
time: `${startTime.format("ah")}点 ${gameLength}`,
|
||||||
qrCodeUrl,
|
qrCodeUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('urlend', url)
|
||||||
// Taro.hideLoading();
|
// Taro.hideLoading();
|
||||||
Taro.showShareImageMenu({
|
Taro.showShareImageMenu({
|
||||||
path: url,
|
path: url,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '首页',
|
navigationBarTitleText: '首页',
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
navigationBarBackgroundColor: '#FAFAFA'
|
navigationBarBackgroundColor: '#FAFAFA',
|
||||||
|
enableShareAppMessage: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { View } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
import Taro from "@tarojs/taro";
|
import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro";
|
||||||
|
import { OSS_BASE } from "@/config/api";
|
||||||
import { wechat_auth_login, save_login_state } from "@/services/loginService";
|
import { wechat_auth_login, save_login_state } from "@/services/loginService";
|
||||||
import { useUserActions } from "@/store/userStore";
|
import { useUserActions } from "@/store/userStore";
|
||||||
import { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
@@ -18,7 +19,11 @@ import { useDictionaryStore } from "@/store/dictionaryStore";
|
|||||||
type TabType = "list" | "message" | "personal";
|
type TabType = "list" | "message" | "personal";
|
||||||
|
|
||||||
const MainPage: React.FC = () => {
|
const MainPage: React.FC = () => {
|
||||||
const [currentTab, setCurrentTab] = useState<TabType>("list");
|
const { params } = useRouter();
|
||||||
|
const [currentTab, setCurrentTab] = useState<TabType>(() => {
|
||||||
|
const tab = params?.tab as TabType | undefined;
|
||||||
|
return tab === "list" || tab === "message" || tab === "personal" ? tab : "list";
|
||||||
|
});
|
||||||
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
|
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
|
||||||
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
|
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
|
||||||
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
|
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
|
||||||
@@ -35,6 +40,14 @@ const MainPage: React.FC = () => {
|
|||||||
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
||||||
useGlobalState();
|
useGlobalState();
|
||||||
|
|
||||||
|
// 从分享链接进入时根据 ?tab= 定位到对应 tab
|
||||||
|
useEffect(() => {
|
||||||
|
const tab = params?.tab as TabType | undefined;
|
||||||
|
if (tab === "list" || tab === "message" || tab === "personal") {
|
||||||
|
setCurrentTab(tab);
|
||||||
|
}
|
||||||
|
}, [params?.tab]);
|
||||||
|
|
||||||
// 初始化:自动微信授权并获取用户信息
|
// 初始化:自动微信授权并获取用户信息
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
@@ -153,6 +166,21 @@ const MainPage: React.FC = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 分享:首页、个人页均支持转发
|
||||||
|
// 分享图:配置 OSS 地址 + 路径(不含 ? 后参数),首页用 share_home.png,个人页用 share_self.png
|
||||||
|
useShareAppMessage(() => {
|
||||||
|
const isList = currentTab === "list";
|
||||||
|
const isPersonal = currentTab === "personal";
|
||||||
|
const title = isList ? "约球 - 发现身边的球局" : isPersonal ? "约球 - 我的约球" : "约球";
|
||||||
|
const image_path = isPersonal ? "system/share_self.png" : "system/share_home.png";
|
||||||
|
const imageUrl = OSS_BASE ? `${OSS_BASE.replace(/\/$/, "")}/${image_path}` : "";
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
path: "/main_pages/index" + (isList ? "?tab=list" : isPersonal ? "?tab=personal" : ""),
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// 滚动到顶部
|
// 滚动到顶部
|
||||||
const scrollToTop = useCallback(() => {
|
const scrollToTop = useCallback(() => {
|
||||||
// 如果当前是列表页,触发列表页内部滚动
|
// 如果当前是列表页,触发列表页内部滚动
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [openPicker, setOpenPicker] = useState(false); //为了解决上传图片时按钮样式问题
|
const [openPicker, setOpenPicker] = useState(false); //为了解决上传图片时按钮样式问题
|
||||||
const [scrollTop, setScrollTop] = useState(0);
|
const [scrollTop, setScrollTop] = useState(0);
|
||||||
const [isTextareaFocused, setIsTextareaFocused] = useState(false); // 记录 TextareaTag 是否 focus
|
|
||||||
const { getDictionaryValue } = useDictionaryActions()
|
const { getDictionaryValue } = useDictionaryActions()
|
||||||
const court_type = getDictionaryValue('court_type') || []
|
const court_type = getDictionaryValue('court_type') || []
|
||||||
const court_surface = getDictionaryValue('court_surface') || []
|
const court_surface = getDictionaryValue('court_surface') || []
|
||||||
@@ -200,13 +199,6 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当键盘显示且 TextareaTag 当前 focus 时才触发 changeTextarea
|
|
||||||
useEffect(() => {
|
|
||||||
if (isKeyboardVisible && isTextareaFocused) {
|
|
||||||
changeTextarea(true)
|
|
||||||
}
|
|
||||||
}, [isKeyboardVisible, isTextareaFocused])
|
|
||||||
|
|
||||||
const changePicker = (value:boolean) => {
|
const changePicker = (value:boolean) => {
|
||||||
setOpenPicker(value);
|
setOpenPicker(value);
|
||||||
}
|
}
|
||||||
@@ -272,11 +264,9 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
updateFormData(item.prop, value)
|
updateFormData(item.prop, value)
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
// onBlur={() => {
|
||||||
setIsTextareaFocused(false)
|
// }}
|
||||||
}}
|
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setIsTextareaFocused(true)
|
|
||||||
changeTextarea(true)
|
changeTextarea(true)
|
||||||
}}
|
}}
|
||||||
placeholder='有其他场地信息可备注'
|
placeholder='有其他场地信息可备注'
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class GameDetailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getLinkUrl(req: { path: string, query: string }): Promise<ApiResponse<{ url_link: string, path: string, query: string }>> {
|
async getLinkUrl(req: { path: string, query: string }): Promise<ApiResponse<{ url_link: string, path: string, query: string }>> {
|
||||||
return httpService.post('/user/generate_url_link', req, { showLoading: true })
|
return httpService.post('/user/generate_url_link', req, { showLoading: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 |
@@ -150,12 +150,13 @@ export const useKeyboardStore = create<KeyboardStore>((set, get) => ({
|
|||||||
|
|
||||||
// 导出便捷的 hooks
|
// 导出便捷的 hooks
|
||||||
export const useKeyboardHeight = () => {
|
export const useKeyboardHeight = () => {
|
||||||
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardStore()
|
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardStore()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keyboardHeight,
|
keyboardHeight,
|
||||||
isKeyboardVisible,
|
isKeyboardVisible,
|
||||||
addListener,
|
addListener,
|
||||||
initializeKeyboardListener
|
initializeKeyboardListener,
|
||||||
|
setKeyboardVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,27 @@ function getImageWh(src: string): Promise<{ width: number; height: number }> {
|
|||||||
|
|
||||||
/** 加载图片 */
|
/** 加载图片 */
|
||||||
function loadImage(canvas: any, src: string): Promise<any> {
|
function loadImage(canvas: any, src: string): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
let timer: any;
|
||||||
const img = canvas.createImage();
|
const img = canvas.createImage();
|
||||||
img.onload = () => resolve(img);
|
|
||||||
|
img.crossOrigin = "anonymous"
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
console.log('img error', src)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
reject(new Error(`Image load timeout: ${src}`));
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
img.src = src;
|
img.src = src;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,6 +164,7 @@ async function drawRotateCoverImage(
|
|||||||
rotate = 0 // 旋转角度(弧度)
|
rotate = 0 // 旋转角度(弧度)
|
||||||
) {
|
) {
|
||||||
const { width, height } = await getImageWh(src);
|
const { width, height } = await getImageWh(src);
|
||||||
|
console.log('width', width, 'height', height)
|
||||||
const scale = Math.max(w / width, h / height);
|
const scale = Math.max(w / width, h / height);
|
||||||
const newW = width * scale;
|
const newW = width * scale;
|
||||||
const newH = height * scale;
|
const newH = height * scale;
|
||||||
@@ -179,6 +198,7 @@ async function drawRotateCoverImage(
|
|||||||
|
|
||||||
// 绘制 cover
|
// 绘制 cover
|
||||||
ctx.drawImage(img, offsetX, offsetY, newW, newH);
|
ctx.drawImage(img, offsetX, offsetY, newW, newH);
|
||||||
|
console.log('drawImage', offsetX, offsetY, newW, newH)
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
@@ -290,6 +310,8 @@ function drawTextWrap(
|
|||||||
|
|
||||||
/** 核心纯函数:生成海报图片 */
|
/** 核心纯函数:生成海报图片 */
|
||||||
export async function generatePosterImage(data: any): Promise<string> {
|
export async function generatePosterImage(data: any): Promise<string> {
|
||||||
|
|
||||||
|
|
||||||
console.log("start !!!!");
|
console.log("start !!!!");
|
||||||
// const dpr = Taro.getWindowInfo().pixelRatio;
|
// const dpr = Taro.getWindowInfo().pixelRatio;
|
||||||
const dpr = 1;
|
const dpr = 1;
|
||||||
@@ -297,19 +319,30 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
const width = 600;
|
const width = 600;
|
||||||
const height = 1000;
|
const height = 1000;
|
||||||
|
|
||||||
|
console.log('width', width, 'height', height)
|
||||||
const canvas = Taro.createOffscreenCanvas({ type: "2d", width: width * dpr, height: height * dpr });
|
const canvas = Taro.createOffscreenCanvas({ type: "2d", width: width * dpr, height: height * dpr });
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
ctx.scale(dpr, dpr);
|
ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
|
|
||||||
|
console.log('ctx', ctx)
|
||||||
|
|
||||||
|
|
||||||
// 背景渐变
|
// 背景渐变
|
||||||
roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC");
|
roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC");
|
||||||
|
|
||||||
|
console.log('bgUrl', bgUrl)
|
||||||
const bgImg = await loadImage(canvas, bgUrl);
|
const bgImg = await loadImage(canvas, bgUrl);
|
||||||
ctx.drawImage(bgImg, 0, 0, width, height);
|
ctx.drawImage(bgImg, 0, 0, width, height);
|
||||||
|
console.log('bgUrlend', )
|
||||||
|
|
||||||
|
|
||||||
roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6));
|
roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6));
|
||||||
|
|
||||||
|
|
||||||
// 顶部图片
|
// 顶部图片
|
||||||
const mainImg = await loadImage(canvas, data.mainCoursal);
|
const mainImg = await loadImage(canvas, data.mainCoursal);
|
||||||
|
console.log('mainCoursal', data.mainCoursal)
|
||||||
await drawRotateCoverImage(
|
await drawRotateCoverImage(
|
||||||
ctx,
|
ctx,
|
||||||
canvas,
|
canvas,
|
||||||
@@ -378,6 +411,8 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
left = 20;
|
left = 20;
|
||||||
|
|
||||||
const dateImg = await loadImage(canvas, dateIcon);
|
const dateImg = await loadImage(canvas, dateIcon);
|
||||||
|
|
||||||
|
console.log('dateIcon', dateIcon)
|
||||||
await drawCoverImage(
|
await drawCoverImage(
|
||||||
ctx,
|
ctx,
|
||||||
canvas,
|
canvas,
|
||||||
@@ -404,6 +439,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
left = 20;
|
left = 20;
|
||||||
top += 24;
|
top += 24;
|
||||||
|
|
||||||
|
|
||||||
const mapImg = await loadImage(canvas, mapIcon);
|
const mapImg = await loadImage(canvas, mapIcon);
|
||||||
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12);
|
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12);
|
||||||
|
|
||||||
@@ -440,11 +476,13 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
ctx.font = "400 20px sans-serif";
|
ctx.font = "400 20px sans-serif";
|
||||||
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
|
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
|
||||||
|
|
||||||
|
console.log('canvas', canvas)
|
||||||
// 导出图片
|
// 导出图片
|
||||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||||
canvas,
|
canvas,
|
||||||
fileType: 'png',
|
fileType: 'png',
|
||||||
quality: 0.7,
|
quality: 0.7,
|
||||||
});
|
});
|
||||||
|
console.log('tempFilePath', tempFilePath)
|
||||||
return tempFilePath;
|
return tempFilePath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -533,7 +533,6 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`)
|
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`)
|
||||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
||||||
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
||||||
|
|||||||
Reference in New Issue
Block a user