From bceca4f1fa7651919023e57786352fb318b0de66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Mon, 18 Aug 2025 14:21:15 +0800 Subject: [PATCH 01/22] feat: carousel and set text color according to bg color --- project.private.config.json | 23 ++++++ src/app.config.ts | 3 +- src/pages/detail/index.config.ts | 4 ++ src/pages/detail/index.scss | 90 ++++++++++++++++++++++++ src/pages/detail/index.tsx | 117 +++++++++++++++++++++++++++++++ src/pages/index/index.tsx | 79 +++++++++++---------- src/utils/processImage.ts | 33 +++++++++ 7 files changed, 312 insertions(+), 37 deletions(-) create mode 100644 project.private.config.json create mode 100644 src/pages/detail/index.config.ts create mode 100644 src/pages/detail/index.scss create mode 100644 src/pages/detail/index.tsx create mode 100644 src/utils/processImage.ts diff --git a/project.private.config.json b/project.private.config.json new file mode 100644 index 0000000..2c19aa8 --- /dev/null +++ b/project.private.config.json @@ -0,0 +1,23 @@ +{ + "libVersion": "3.9.0", + "projectname": "playBallTogether", + "condition": {}, + "setting": { + "urlCheck": true, + "coverView": true, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": false, + "preloadBackgroundData": false, + "autoAudits": false, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": false, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "compileHotReLoad": true, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false + } +} \ No newline at end of file diff --git a/src/app.config.ts b/src/app.config.ts index 15c683b..2096fcf 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,6 +1,7 @@ export default defineAppConfig({ pages: [ - 'pages/index/index' + 'pages/index/index', + 'pages/detail/index', ], window: { backgroundTextStyle: 'light', diff --git a/src/pages/detail/index.config.ts b/src/pages/detail/index.config.ts new file mode 100644 index 0000000..eaeff9d --- /dev/null +++ b/src/pages/detail/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '球局详情', + navigationStyle: 'custom', +}) diff --git a/src/pages/detail/index.scss b/src/pages/detail/index.scss new file mode 100644 index 0000000..ce17fc4 --- /dev/null +++ b/src/pages/detail/index.scss @@ -0,0 +1,90 @@ +.detail-page { + width: 100%; + height: 100%; + + .custom-navbar { + height: 56px; /* 通常与原生导航栏高度一致 */ + display: flex; + align-items: center; + justify-content: center; + // background-color: #fff; + color: #000; + padding-top: 36px; /* 适配状态栏 */ + } + + .detail-navigator { + height: 30px; + width: 80px; + border-radius: 15px; + position: absolute; + left: 12px; + border: 1px solid #888; + box-sizing: border-box; + color: #fff; + display: flex; + align-items: center; + + .detail-navigator-back, .detail-navigator-icon { + height: 20px; + width: 50%; + + display: flex; + justify-content: center; + + & > svg { + width: 20px; + height: 20px; + color: #fff; + } + } + } + + .detail-page-bg { + width: 100%; + height: 100%; + position: fixed; + left: 0; + top: 0; + background-size: cover; + filter: blur(40px); + transform: scale(1.5); + z-index: -2; + width: calc(100% + 20px); + height: calc(100% + 20px); + margin: -10px; + } + + .detail-page-bg-text { + width: 100%; + height: 100%; + position: fixed; + left: 0; + top: 0; + z-index: -1; + background-color: rgba(0, 0, 0, 0.3); + } + + .detail-swiper { + height: 240px; + margin-top: 15px; + margin-left: 15px; + } + + .detail-swiper-item { + overflow: visible; + height: 100%; + + .detail-swiper-item-image { + width: 100%; + height: 100%; + border-radius: 12px; + transition: transform 0.5s; + } + } + + .detail-text { + font-size: 40rpx; + margin-top: 20px; + transition: color 0.3s ease-in; + } +} diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx new file mode 100644 index 0000000..7c94d32 --- /dev/null +++ b/src/pages/detail/index.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react' +import { View, Text, Button, Swiper, SwiperItem, Image } from '@tarojs/components' +import { Cell, Avatar, Progress } from '@nutui/nutui-react-taro' +import Taro from '@tarojs/taro' +// 导入API服务 +import demoApi from '../../services/demoApi' +import commonApi from '../../services/commonApi' +import { + useUserStats, + useUserActions +} from '../../store/userStore' +import { getTextColorOnImage } from '../../utils/processImage' +import './index.scss' + +const images = [ + 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png', + 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/cf5a82ba-90af-4138-a1b3-9119adcde9e0.png', + 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/49d7cdf0-b03c-4a0f-91c6-e7778080cfcd.png' +] + + + +function Index() { + // 使用Zustand store + // const userStats = useUserStats() + // const { incrementRequestCount, resetUserStats } = useUserActions() + + const [current, setCurrent] = useState(0) + const [colors, setColors] = useState([]) + + // 本地状态管理 + const [loading, setLoading] = useState(false) + const [userProfile, setUserProfile] = useState(null) + const [interests, setInterests] = useState([]) + + // 页面加载时获取数据 + useEffect(() => { + initializeData() + calcBgMainColors() + }, []) + + // 初始化数据 + const initializeData = async () => { + try { + // 获取推荐的兴趣爱好 + const interestsRes = await demoApi.getRecommendedInterests() + if (interestsRes.success) { + setInterests(interestsRes.data || []) + } + } catch (error) { + console.log('获取初始数据失败:', error) + } + } + + const calcBgMainColors = async () => { + const textcolors: string[] = [] + for (const index in images) { + const { textColor } = await getTextColorOnImage(images[index]) + textcolors[index] = textColor + } + setColors(textcolors) + } + + return ( + + + + + + + + + + + + + + + + + + + {/* 我的自定义标题 */} + + + + { setCurrent(e.detail.current) }} + > + {images.map((imageUrl, index) => ( + + + + ))} + + + Five feet from the bed was an earthen wall that had suffered from numerous cracks due to the passage of time. From the other side of the wall came the nagging voice of his mother and the occasional deep breathing of his father who was smoking his pipe. + + + ) +} + +export default Index diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index bf32306..8c59767 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -5,8 +5,8 @@ import Taro from '@tarojs/taro' // 导入API服务 import demoApi from '../../services/demoApi' import commonApi from '../../services/commonApi' -import { - useUserStats, +import { + useUserStats, useUserActions } from '../../store/userStore' import './index.scss' @@ -15,7 +15,7 @@ function Index() { // 使用Zustand store const userStats = useUserStats() const { incrementRequestCount, resetUserStats } = useUserActions() - + // 本地状态管理 const [loading, setLoading] = useState(false) const [userProfile, setUserProfile] = useState(null) @@ -43,19 +43,19 @@ function Index() { const handleGetUserProfile = async () => { console.log('获取用户信息...'); setLoading(true) - + try { const response = await demoApi.getUserProfile() - + if (response.success) { setUserProfile(response.data) incrementRequestCount() - + Taro.showToast({ title: '获取用户信息成功', icon: 'success' }) - + console.log('用户信息:', response.data) } } catch (error) { @@ -64,7 +64,7 @@ function Index() { title: '获取失败,使用模拟数据', icon: 'none' }) - + // 模拟数据 setUserProfile({ id: '123', @@ -83,7 +83,7 @@ function Index() { const handleSubmitStats = async () => { console.log('提交统计数据...'); setLoading(true) - + try { const response = await commonApi.submitForm('userStats', [ { @@ -97,21 +97,21 @@ function Index() { } } ]) - + if (response.success) { incrementRequestCount() - + Taro.showToast({ title: '统计数据提交成功', icon: 'success' }) - + console.log('提交结果:', response.data) } } catch (error) { console.error('提交统计数据失败:', error) incrementRequestCount() // 即使失败也计数,用于演示 - + Taro.showToast({ title: '网络模拟提交成功', icon: 'success' @@ -125,7 +125,7 @@ function Index() { const handleSubmitFeedback = async () => { console.log('提交用户反馈...'); setLoading(true) - + try { const response = await demoApi.submitFeedback({ matchId: 'demo_match_' + Date.now(), @@ -134,21 +134,21 @@ function Index() { aspects: ['场地环境', '服务质量', '价格合理'], comments: `用户反馈 - 请求次数: ${userStats.requestCount + 1},体验良好!` }) - + if (response.success) { incrementRequestCount() - + Taro.showToast({ title: '反馈提交成功', icon: 'success' }) - + console.log('反馈结果:', response.data) } } catch (error) { console.error('提交反馈失败:', error) incrementRequestCount() // 即使失败也计数,用于演示 - + Taro.showToast({ title: '网络模拟提交成功', icon: 'success' @@ -163,15 +163,22 @@ function Index() { console.log('重置所有数据...'); resetUserStats() setUserProfile(null) - + Taro.showToast({ title: '数据已重置', icon: 'success' }) } + const handleDetail = () => { + Taro.navigateTo({ + url: '/pages/detail/index' + }) + } + return ( + {/* 页面标题 */} API 请求演示 @@ -181,9 +188,9 @@ function Index() { {/* 用户信息卡片 */} - {userProfile?.nickname?.charAt(0) || 'U'} @@ -205,17 +212,17 @@ function Index() { {/* 统计数据 */} 📊 API 请求统计 - + - {interests.length > 0 && ( - )} @@ -224,10 +231,10 @@ function Index() { {/* API 请求按钮区域 */} 🚀 API 请求功能 - + - {/* 页面标题 */} API 请求演示 diff --git a/yarn.lock b/yarn.lock index 54ae1ea..e25d73d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2885,7 +2885,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3085,18 +3085,6 @@ arrify@^1.0.1: resolved "https://registry.npmmirror.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -3136,16 +3124,6 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.13.2" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" - integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== - axios@^1.6.8: version "1.11.0" resolved "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" @@ -3276,13 +3254,6 @@ batch@0.6.1: resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - big.js@^5.2.2: version "5.2.2" resolved "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3525,11 +3496,6 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - caw@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" @@ -3713,17 +3679,6 @@ color-name@~1.1.4: resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-thief-react@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/color-thief-react/-/color-thief-react-2.1.0.tgz#7edcf91663d76fc58fee8c9887621d56786672d6" - integrity sha512-2Vr8XnxzoSEz2nUswROWIrxI6joMom4S9TZ5RC69/FR2K87BuXUZk5C4qn6g/rq5bYKx9m+HNjwlCOWIYMAv2A== - dependencies: - color-convert "^2.0.1" - colorthief "2.3.2" - prop-types "15.7.2" - tslib "2.3.0" - use-current-effect "2.1.0" - colord@^2.9.3: version "2.9.3" resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" @@ -3734,15 +3689,7 @@ colorette@^2.0.10: resolved "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -colorthief@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/colorthief/-/colorthief-2.3.2.tgz#00b984f421abe5a2af71c4d464c9d80d407fd53d" - integrity sha512-1r4nPW553JviRcFRvN3fS2V9nUSQGjRIws8UfEeFLIxk8j1tvtaX+AAYTkH3A4B5Muiys8SA1WJxf+00xVTXyg== - dependencies: - get-pixels "^3.3.2" - quantize "github:lokesh/quantize" - -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3894,11 +3841,6 @@ core-js@^3.36.1: resolved "https://registry.npmmirror.com/core-js/-/core-js-3.45.0.tgz#556c2af44a2d9c73ea7b49504392474a9f7c947e" integrity sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA== -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -4117,25 +4059,6 @@ cuint@^0.2.2: resolved "https://registry.npmmirror.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw== -cwise-compiler@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5" - integrity sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ== - dependencies: - uniq "^1.0.0" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - -data-uri-to-buffer@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz#18ae979a6a0ca994b0625853916d2662bbae0b1a" - integrity sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw== - data-urls@^5.0.0: version "5.0.0" resolved "https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" @@ -4556,14 +4479,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5206,21 +5121,6 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -5434,11 +5334,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - form-data@^4.0.0, form-data@^4.0.4: version "4.0.4" resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" @@ -5450,15 +5345,6 @@ form-data@^4.0.0, form-data@^4.0.4: hasown "^2.0.2" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -5568,23 +5454,6 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" -get-pixels@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/get-pixels/-/get-pixels-3.3.3.tgz#71e2dfd4befb810b5478a61c6354800976ce01c7" - integrity sha512-5kyGBn90i9tSMUVHTqkgCHsoWoR+/lGbl4yC83Gefyr0HLIhgSWEx/2F/3YgsZ7UpYNuM6pDhDK7zebrUJ5nXg== - dependencies: - data-uri-to-buffer "0.0.3" - jpeg-js "^0.4.1" - mime-types "^2.0.1" - ndarray "^1.0.13" - ndarray-pack "^1.1.1" - node-bitmap "0.0.1" - omggif "^1.0.5" - parse-data-uri "^0.2.0" - pngjs "^3.3.3" - request "^2.44.0" - through "^2.3.4" - get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" @@ -5648,13 +5517,6 @@ get-tsconfig@^4.7.0: dependencies: resolve-pkg-maps "^1.0.0" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" - git-clone@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/git-clone/-/git-clone-0.1.0.tgz#0d76163778093aef7f1c30238f2a9ef3f07a2eb9" @@ -5834,19 +5696,6 @@ handle-thing@^2.0.0: resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -6102,15 +5951,6 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - https-proxy-agent@^7.0.5: version "7.0.6" resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -6268,11 +6108,6 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -iota-array@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087" - integrity sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -6330,7 +6165,7 @@ is-boolean-object@^1.2.1: call-bound "^1.0.3" has-tostringtag "^1.0.2" -is-buffer@^1.0.2, is-buffer@~1.1.6: +is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -6544,11 +6379,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: dependencies: which-typed-array "^1.1.16" -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" @@ -6606,11 +6436,6 @@ isobject@^3.0.1: resolved "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.npmmirror.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" @@ -6701,11 +6526,6 @@ joi@^17.12.3: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -jpeg-js@^0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" - integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -6718,11 +6538,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - jsdom@^24.0.0: version "24.1.3" resolved "https://registry.npmmirror.com/jsdom/-/jsdom-24.1.3.tgz#88e4a07cb9dd21067514a619e9f17b090a394a9f" @@ -6785,21 +6600,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.1, json5@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -6835,16 +6640,6 @@ jsonp-retry@^1.0.3: dependencies: object-assign "^4.1.1" -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.5" resolved "https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" @@ -7323,7 +7118,7 @@ mime-db@1.52.0: resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.0.1, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -7509,22 +7304,6 @@ natural-compare@^1.4.0: resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -ndarray-pack@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ndarray-pack/-/ndarray-pack-1.2.1.tgz#8caebeaaa24d5ecf70ff86020637977da8ee585a" - integrity sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g== - dependencies: - cwise-compiler "^1.1.2" - ndarray "^1.0.13" - -ndarray@^1.0.13: - version "1.0.19" - resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e" - integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ== - dependencies: - iota-array "^1.0.0" - is-buffer "^1.0.2" - negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -7560,11 +7339,6 @@ node-addon-api@^7.0.0: resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== -node-bitmap@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/node-bitmap/-/node-bitmap-0.0.1.tgz#180eac7003e0c707618ef31368f62f84b2a69091" - integrity sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA== - node-forge@^1: version "1.3.1" resolved "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -7646,11 +7420,6 @@ nwsapi@^2.2.12: resolved "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.21.tgz#8df7797079350adda208910d8c33fc4c2d7520c3" integrity sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7722,11 +7491,6 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -omggif@^1.0.5: - version "1.0.10" - resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" - integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== - on-finished@2.4.1: version "2.4.1" resolved "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -7912,13 +7676,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-data-uri@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" - integrity sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w== - dependencies: - data-uri-to-buffer "0.0.3" - parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -8032,11 +7789,6 @@ pend@~1.2.0: resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - picocolors@^0.2.1: version "0.2.1" resolved "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" @@ -8101,11 +7853,6 @@ platform@^1.3.6: resolved "https://registry.npmmirror.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== -pngjs@^3.3.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== - possible-typed-array-names@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" @@ -8479,15 +8226,6 @@ promise-polyfill@^7.1.0: resolved "https://registry.npmmirror.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b" integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ== -prop-types@15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.8.1" - prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -8525,7 +8263,7 @@ prr@~1.0.1: resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.15.0" resolved "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== @@ -8552,15 +8290,6 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - -"quantize@github:lokesh/quantize": - version "1.4.0" - resolved "https://codeload.github.com/lokesh/quantize/tar.gz/270c6f81623c3247eed50ba37b9d74d79af7a267" - query-string@^5.0.1: version "5.1.1" resolved "https://registry.npmmirror.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -8634,7 +8363,7 @@ react-dom@^18.0.0: loose-envify "^1.1.0" scheduler "^0.23.2" -react-is@^16.13.1, react-is@^16.8.1: +react-is@^16.13.1: version "16.13.1" resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -8844,32 +8573,6 @@ renderkid@^3.0.0: lodash "^4.17.21" strip-ansi "^6.0.1" -request@^2.44.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -9018,7 +8721,7 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -9045,7 +8748,7 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -9491,21 +9194,6 @@ split-on-first@^3.0.0: resolved "https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== -sshpk@^1.7.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" - integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - stackframe@^1.3.4: version "1.3.4" resolved "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" @@ -9908,7 +9596,7 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -through@^2.3.4, through@^2.3.6, through@^2.3.8: +through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.npmmirror.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -9969,14 +9657,6 @@ tough-cookie@^4.1.4: universalify "^0.2.0" url-parse "^1.5.3" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^5.1.0: version "5.1.1" resolved "https://registry.npmmirror.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" @@ -10049,11 +9729,6 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== - tslib@^1.10.0: version "1.14.1" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -10071,11 +9746,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -10239,11 +9909,6 @@ unicorn-magic@^0.3.0: resolved "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz#4efd45c85a69e0dd576d25532fbfa22aa5c8a104" integrity sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== -uniq@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== - universal-router@^9.2.0: version "9.2.1" resolved "https://registry.npmmirror.com/universal-router/-/universal-router-9.2.1.tgz#38e267770f75655c853123b81bcdd6be1bfc31a5" @@ -10325,11 +9990,6 @@ url-to-options@^1.0.1: resolved "https://registry.npmmirror.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== -use-current-effect@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/use-current-effect/-/use-current-effect-2.1.0.tgz#8b0074da370cbc3b756cedbd041806fd53d870af" - integrity sha512-yxSWZsBfohTulb+HyLdSIcUoG5UnSbgNl5SAEtFhzq1yqTeJAa32dZhhO5ACYn7Lc2IGt03pUOkUZZQQX/ReXg== - use-sync-external-store@^1.2.2: version "1.5.0" resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" @@ -10350,11 +10010,6 @@ utils-merge@1.0.1: resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -10393,15 +10048,6 @@ vary@~1.1.2: resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vm2@^3.9.19: version "3.9.19" resolved "https://registry.npmmirror.com/vm2/-/vm2-3.9.19.tgz#be1e1d7a106122c6c492b4d51c2e8b93d3ed6a4a" From 7a7ab85a8220557c72933ccd1d5f9e886d002c39 Mon Sep 17 00:00:00 2001 From: juguohong Date: Tue, 19 Aug 2025 00:43:29 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CityFilter/index.module.scss | 1 + src/components/CityFilter/index.tsx | 11 +-- src/components/Menu/index.module.scss | 2 + src/components/SearchBar/index.module.scss | 1 - src/components/SearchBar/index.tsx | 24 +++--- src/config/images.js | 1 + src/pages/list/FilterPopup.tsx | 23 +++++- src/pages/list/filterPopup.module.scss | 27 +++++++ src/pages/list/index.module.scss | 15 ++++ src/pages/list/index.scss | 0 src/pages/list/index.tsx | 82 +++++++++++++-------- src/static/list/icon-filter.svg | 11 +++ src/store/listStore.ts | 60 +++++++-------- 13 files changed, 174 insertions(+), 84 deletions(-) create mode 100644 src/pages/list/index.module.scss delete mode 100644 src/pages/list/index.scss create mode 100644 src/static/list/icon-filter.svg diff --git a/src/components/CityFilter/index.module.scss b/src/components/CityFilter/index.module.scss index aa8442f..ef6d454 100644 --- a/src/components/CityFilter/index.module.scss +++ b/src/components/CityFilter/index.module.scss @@ -1,6 +1,7 @@ .menuWrap { padding: 5px 20px 10px; .menuItem { + width: 100vw; left: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; diff --git a/src/components/CityFilter/index.tsx b/src/components/CityFilter/index.tsx index db40f86..b50c8ca 100644 --- a/src/components/CityFilter/index.tsx +++ b/src/components/CityFilter/index.tsx @@ -12,7 +12,7 @@ interface IProps { } const MenuComponent = (props: IProps) => { - const { value, onChange, wrapperClassName, itemClassName } = props; + const { value, onChange, wrapperClassName, itemClassName, options } = props; const [isChange, setIsChange] = useState(false); const itemRef = useRef(null); @@ -22,12 +22,7 @@ const MenuComponent = (props: IProps) => { onChange && onChange(value); }; - const options: BubbleOption[] = [ - { id: 0, label: "全城", value: "0" }, - { id: 1, label: "3km", value: "3" }, - { id: 2, label: "5km", value: "5" }, - { id: 3, label: "10km", value: "10" }, - ]; + return ( { >
diff --git a/src/components/Menu/index.module.scss b/src/components/Menu/index.module.scss index 0867087..4bebabb 100644 --- a/src/components/Menu/index.module.scss +++ b/src/components/Menu/index.module.scss @@ -1,6 +1,8 @@ .menuWrap { + position: static; padding: 5px 20px 10px; .menuItem { + width: 100vw; left: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; diff --git a/src/components/SearchBar/index.module.scss b/src/components/SearchBar/index.module.scss index b376efc..632fe92 100644 --- a/src/components/SearchBar/index.module.scss +++ b/src/components/SearchBar/index.module.scss @@ -6,7 +6,6 @@ --nutui-searchbar-input-text-color: #000000; --nutui-searchbar-input-padding: 0 0 0 10px; --nutui-searchbar-padding:0 15px; - // --nutui-searchbar-background: #ffffff; :global(.nut-searchbar-content) { box-shadow: 0 4px 48px #00000014; } diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 60fd06f..9456d55 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -1,22 +1,26 @@ +import { SearchBar } from "@nutui/nutui-react-taro"; +// import {ICON_FILTER} from '../../config/images.js' +import styles from "./index.module.scss"; -import { SearchBar } from '@nutui/nutui-react-taro' -import styles from './index.module.scss' - -const SearchBarComponent = () => { +const SearchBarComponent = (props: IProps) => { + // console.log('===', ICON_FILTER) + const { handleFilterIcon } = props; return ( <> - 123
// } right={ -
+
+ 筛 +
} className={styles.searchBar} - placeholder='搜索上海的球局和场地' + placeholder="搜索上海的球局和场地" /> - ) -} + ); +}; -export default SearchBarComponent \ No newline at end of file +export default SearchBarComponent; diff --git a/src/config/images.js b/src/config/images.js index ab20192..2f42a82 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -8,4 +8,5 @@ export default { ICON_COST: require('@/static/publishBall/icon-cost.svg'), ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), + ICON_FILTER: require('@/static/list/icon-filter.svg'), } \ No newline at end of file diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index 1e31276..a4c19f3 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -3,6 +3,8 @@ import Range from "../../components/Range"; import Bubble, { BubbleOption } from "../../components/Bubble"; import styles from "./filterPopup.module.scss"; import TitleComponent from "src/components/Title"; +import { Button } from "@nutui/nutui-react-taro"; +import { useListStore } from "../../store/listStore"; const timeOptions: BubbleOption[] = [ { id: 1, label: "晨间 6:00-10:00", value: "morning" }, @@ -19,7 +21,14 @@ const locationOptions: BubbleOption[] = [ { id: 3, label: "半室外", value: "3" }, ]; -const FilterPopup = () => { +interface IProps { + onCancel: () => void; + onConfirm: () => void; + loading: boolean; +} + +const FilterPopup = (props: IProps) => { + const { loading, onCancel, onConfirm } = props; return ( <> { position="top" round closeOnOverlayClick={false} - onClose={() => { - // setShowTop(false) - }} >
{/* 时间气泡选项 */} @@ -63,6 +69,15 @@ const FilterPopup = () => { columns={3} />
+ {/* 按钮 */} +
+ + +
diff --git a/src/pages/list/filterPopup.module.scss b/src/pages/list/filterPopup.module.scss index c1e834a..9305f21 100644 --- a/src/pages/list/filterPopup.module.scss +++ b/src/pages/list/filterPopup.module.scss @@ -1,8 +1,35 @@ .filterPopupWrapper { + position: relative; $m18: 18px; padding: $m18; + .filterPopupRange { margin-top: $m18; margin-bottom: $m18; } + + .filterPopupBtnWrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + position: sticky; + bottom: 0; + background-color: #ffffff; + padding: 8px 0; + + .btn { + flex: 1; + } + + --nutui-button-border-width: 0.5px; + --nutui-button-default-border-color: #0000000F; + --nutui-button-border-radius: 16px; + --nutui-button-default-height: 44px; + --nutui-button-default-color: #000000; + .confirm { + --nutui-button-default-color: #ffffff; + --nutui-button-default-background-color: #000000; + } + } } \ No newline at end of file diff --git a/src/pages/list/index.module.scss b/src/pages/list/index.module.scss new file mode 100644 index 0000000..1aa4c24 --- /dev/null +++ b/src/pages/list/index.module.scss @@ -0,0 +1,15 @@ +.listPage { + background-color: #fafafa; + padding: 0 15px; + + .listTopFilterWrapper { + display: flex; + align-items: center; + padding: 5px 0 10px; + gap: 5px; + } + + .menuFilter { + padding: 0; + } +} diff --git a/src/pages/list/index.scss b/src/pages/list/index.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index 7587ed3..525fddc 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -1,29 +1,26 @@ import ListItem from "../../components/ListItem"; import List from "../../components/List"; -import Bubble from "../../components/Bubble/example"; -import Range from "../../components/Range/example"; -import Menu from "../../components/Menu/example"; -import CityFilter from "../../components/CityFilter/example"; +import Menu from "../../components/Menu"; +import CityFilter from "../../components/CityFilter"; import SearchBar from "../../components/SearchBar"; import FilterPopup from "./FilterPopup"; -import "./index.scss"; +import styles from "./index.module.scss"; import { useEffect } from "react"; import Taro from "@tarojs/taro"; -import { - useTennisMatches, - useTennisLoading, - useTennisError, - useTennisLastRefresh, - useTennisActions, -} from "../../store/listStore"; +import { useListStore } from "../../store/listStore"; const ListPage = () => { // 从 store 获取数据和方法 - const matches = useTennisMatches(); - const loading = useTennisLoading(); - const error = useTennisError(); - const lastRefreshTime = useTennisLastRefresh(); - const { fetchMatches, refreshMatches, clearError } = useTennisActions(); + const { + isShowFilterPopup, + error, + matches, + loading, + fetchMatches, + refreshMatches, + clearError, + updateState, + } = useListStore() || {}; useEffect(() => { // 页面加载时获取数据 @@ -146,22 +143,43 @@ const ListPage = () => { ); } - return ( -
- - {/* 综合筛选 */} -
- -
- {/* 筛选 */} -
- {/* 全城筛选 */} - - {/* 智能排序 */} - -
+ const toggleShowPopup = () => { + updateState({ isShowFilterPopup: !isShowFilterPopup }); + }; - + const cityOptions: BubbleOption[] = [ + { id: 0, label: "全城", value: "0" }, + { id: 1, label: "3km", value: "3" }, + { id: 2, label: "5km", value: "5" }, + { id: 3, label: "10km", value: "10" }, + ]; + + const options = [ + { text: "默认排序", value: "a" }, + { text: "好评排序", value: "b" }, + { text: "销量排序", value: "c" }, + ]; + + return ( +
+ + {/* 综合筛选 */} + {isShowFilterPopup && ( +
+ +
+ )} + {/* 筛选 */} +
+ {/* 全城筛选 */} + { }} wrapperClassName={styles.menuFilter} /> + {/* 智能排序 */} + { }} wrapperClassName={styles.menuFilter} /> +
{/* 列表内容 */} diff --git a/src/static/list/icon-filter.svg b/src/static/list/icon-filter.svg new file mode 100644 index 0000000..dd5b39a --- /dev/null +++ b/src/static/list/icon-filter.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/store/listStore.ts b/src/store/listStore.ts index d64d49e..3a61d67 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -16,15 +16,16 @@ export interface TennisMatch { } // Store 状态接口 -interface TennisState { +interface ListState { matches: TennisMatch[] loading: boolean error: string | null lastRefreshTime: string | null + isShowFilterPopup: boolean } // Store Actions 接口 -interface TennisActions { +interface ListActions { fetchMatches: (params?: { page?: number pageSize?: number @@ -33,36 +34,39 @@ interface TennisActions { }) => Promise refreshMatches: () => Promise clearError: () => void + updateState: (payload: Record) => void } // 完整的 Store 类型 -type TennisStore = TennisState & TennisActions +type TennisStore = ListState & ListActions // 创建 store -export const useTennisStore = create()((set, get) => ({ +export const useListStore = create()((set, get) => ({ // 初始状态 matches: [], loading: false, error: null, lastRefreshTime: null, + // 是否展示综合筛选弹窗 + isShowFilterPopup: false, // 获取比赛数据 fetchMatches: async (params) => { set({ loading: true, error: null }) - + try { const matches = await getTennisMatches(params) - set({ - matches, - loading: false, - lastRefreshTime: new Date().toISOString() + set({ + matches, + loading: false, + lastRefreshTime: new Date().toISOString() }) console.log('Store: 成功获取网球比赛数据:', matches.length, '条') } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' - set({ - error: errorMessage, - loading: false + set({ + error: errorMessage, + loading: false }) console.error('Store: 获取网球比赛数据失败:', errorMessage) } @@ -71,20 +75,20 @@ export const useTennisStore = create()((set, get) => ({ // 刷新比赛数据 refreshMatches: async () => { set({ loading: true, error: null }) - + try { const matches = await getTennisMatches() - set({ - matches, - loading: false, - lastRefreshTime: new Date().toISOString() + set({ + matches, + loading: false, + lastRefreshTime: new Date().toISOString() }) console.log('Store: 成功刷新网球比赛数据:', matches.length, '条') } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' - set({ - error: errorMessage, - loading: false + set({ + error: errorMessage, + loading: false }) console.error('Store: 刷新网球比赛数据失败:', errorMessage) } @@ -93,16 +97,14 @@ export const useTennisStore = create()((set, get) => ({ // 清除错误信息 clearError: () => { set({ error: null }) + }, + // 更新store数据 + updateState: (payload: Record) => { + set({ + ...(payload || {}) + }) } })) // 导出便捷的 hooks -export const useTennisMatches = () => useTennisStore((state) => state.matches) -export const useTennisLoading = () => useTennisStore((state) => state.loading) -export const useTennisError = () => useTennisStore((state) => state.error) -export const useTennisLastRefresh = () => useTennisStore((state) => state.lastRefreshTime) -export const useTennisActions = () => useTennisStore((state) => ({ - fetchMatches: state.fetchMatches, - refreshMatches: state.refreshMatches, - clearError: state.clearError -})) +export const useListState = () => useListStore((state) => state) From e6124131e7a9989a8065e86bb3d45de4696dfe4f Mon Sep 17 00:00:00 2001 From: juguohong Date: Sat, 23 Aug 2025 15:40:19 +0800 Subject: [PATCH 06/22] =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BB=BC=E5=90=88?= =?UTF-8?q?=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Bubble/index.tsx | 7 ++- src/components/CityFilter/index.module.scss | 14 +++++- src/components/List/index.scss | 1 - src/components/Range/index.tsx | 19 ++++---- src/components/SearchBar/index.module.scss | 33 +++++++++++-- src/components/SearchBar/index.tsx | 35 ++++++++++---- src/config/images.js | 2 + src/pages/list/FilterPopup.tsx | 53 +++++++++++++++------ src/pages/list/index.tsx | 41 +++++++++++++--- src/static/list/icon-filter-selected.svg | 11 +++++ src/static/list/icon-search.svg | 5 ++ src/store/listStore.ts | 46 ++++++++++++++++++ 12 files changed, 219 insertions(+), 48 deletions(-) create mode 100644 src/static/list/icon-filter-selected.svg create mode 100644 src/static/list/icon-search.svg diff --git a/src/components/Bubble/index.tsx b/src/components/Bubble/index.tsx index 264de4e..59af227 100644 --- a/src/components/Bubble/index.tsx +++ b/src/components/Bubble/index.tsx @@ -15,6 +15,7 @@ export interface BubbleProps { options: BubbleOption[]; value?: string | number | (string | number)[]; onChange?: ( + name: string, value: string | number | (string | number)[], option: BubbleOption | BubbleOption[] ) => void; @@ -26,6 +27,7 @@ export interface BubbleProps { itemClassName?: string; style?: React.CSSProperties; disabled?: boolean; + name: string; } const Bubble: React.FC = ({ @@ -40,6 +42,7 @@ const Bubble: React.FC = ({ itemClassName = "", style = {}, disabled = false, + name, }) => { const [selectedValues, setSelectedValues] = useState<(string | number)[]>([]); @@ -74,9 +77,9 @@ const Bubble: React.FC = ({ const selectedOptions = options.filter((opt) => newSelectedValues.includes(opt.value) ); - onChange(newSelectedValues, selectedOptions); + onChange(name, newSelectedValues, selectedOptions); } else { - onChange(option.value, option); + onChange(name, option.value, option); } } }; diff --git a/src/components/CityFilter/index.module.scss b/src/components/CityFilter/index.module.scss index ef6d454..ec1ee63 100644 --- a/src/components/CityFilter/index.module.scss +++ b/src/components/CityFilter/index.module.scss @@ -1,17 +1,24 @@ .menuWrap { padding: 5px 20px 10px; + .menuItem { width: 100vw; left: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; } + + .menuItem { + position: fixed; + } + &.active { .nut-menu-bar { background-color: #000000; color: #ffffff; } } + :global(.nut-menu-bar) { color: #000000; line-height: 1; @@ -23,6 +30,7 @@ line-height: 28px; font-size: 14px; width: max-content; + .nut-menu-title { color: inherit !important; font-weight: 600; @@ -49,13 +57,15 @@ .cityName { font-size: 13px; font-weight: 400; - color: #3C3C43; + color: #3c3c43; } + .distanceWrap { margin-bottom: 16px; width: 100%; } + .distanceBubbleItem { width: auto; } -} +} \ No newline at end of file diff --git a/src/components/List/index.scss b/src/components/List/index.scss index 391a9aa..5742c9e 100644 --- a/src/components/List/index.scss +++ b/src/components/List/index.scss @@ -1,6 +1,5 @@ .list { background: #fafafa; - margin-top: 12px; display: flex; flex-direction: column; gap: 5px; diff --git a/src/components/Range/index.tsx b/src/components/Range/index.tsx index 84fe4b1..e4d0f78 100644 --- a/src/components/Range/index.tsx +++ b/src/components/Range/index.tsx @@ -8,9 +8,10 @@ interface RangeProps { max?: number; step?: number; value?: [number, number]; - onChange?: (value: [number, number]) => void; + onChange?: (name: string, value: [number, number]) => void; disabled?: boolean; className?: string; + name: string; } const NtrpRange: React.FC = ({ @@ -21,16 +22,18 @@ const NtrpRange: React.FC = ({ onChange, disabled = false, className, + name, }) => { const [currentValue, setCurrentValue] = useState<[number, number]>(value); - +console.log('===currentValue', currentValue) useEffect(() => { + console.log('===rrr', value) value && setCurrentValue(value); }, [JSON.stringify(value || [])]); const handleChange = (val: [number, number]) => { setCurrentValue(val); - onChange?.(val); + onChange?.(name, val); }; const marks = useMemo(() => { @@ -50,13 +53,9 @@ const NtrpRange: React.FC = ({ }, [JSON.stringify(currentValue || []), min, max]); return ( -
+
- {/*
-
icon
-

NTRP水平区间

-
*/} - +

{rangContent}

@@ -68,7 +67,7 @@ const NtrpRange: React.FC = ({ min={min} max={max} step={step} - // value={currentValue} + value={currentValue} onEnd={handleChange} disabled={disabled} defaultValue={[min, max]} diff --git a/src/components/SearchBar/index.module.scss b/src/components/SearchBar/index.module.scss index 632fe92..cc20edd 100644 --- a/src/components/SearchBar/index.module.scss +++ b/src/components/SearchBar/index.module.scss @@ -5,18 +5,45 @@ --nutui-searchbar-content-border-radius: 44px; --nutui-searchbar-input-text-color: #000000; --nutui-searchbar-input-padding: 0 0 0 10px; - --nutui-searchbar-padding:0 15px; + --nutui-searchbar-padding: 10px 0 0 0; :global(.nut-searchbar-content) { box-shadow: 0 4px 48px #00000014; } .searchBarRight { + position: relative; width: 44px; height: 44px; border-radius: 50%; - border: 1px solid #0000000F; + border: 1px solid #0000000f; background-color: #ffffff; display: flex; align-items: center; justify-content: center; + &.active { + background-color: #000000; + } } -} \ No newline at end of file + .filterIcon { + width: 20px; + height: 20px; + } + .filterCount { + background-color: #000000; + position: absolute; + width: 18px; + height: 18px; + border: 2px solid #ffffff; + border-radius: 50%; + right: -5px; + bottom: -5px; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + } + .searchIcon { + width: 20px; + height: 20px; + } +} diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 9456d55..9d4827d 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -1,21 +1,38 @@ import { SearchBar } from "@nutui/nutui-react-taro"; -// import {ICON_FILTER} from '../../config/images.js' +import { View, Text, Image } from "@tarojs/components"; +import img from "../../config/images"; import styles from "./index.module.scss"; +interface IProps { + handleFilterIcon: () => void; + isSelect: boolean; + filterCount: number; +} + const SearchBarComponent = (props: IProps) => { - // console.log('===', ICON_FILTER) - const { handleFilterIcon } = props; + const { handleFilterIcon, isSelect, filterCount } = props; return ( <> 123
- // } - right={ -
- 筛 + leftIn={ +
+
} + right={ + + + {filterCount} + + } className={styles.searchBar} placeholder="搜索上海的球局和场地" /> diff --git a/src/config/images.js b/src/config/images.js index 2f42a82..9effb04 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -9,4 +9,6 @@ export default { ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), ICON_FILTER: require('@/static/list/icon-filter.svg'), + ICON_FILTER_SELECTED: require('@/static/list/icon-filter-selected.svg'), + ICON_SEARCH: require('@/static/list/icon-search.svg'), } \ No newline at end of file diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index a4c19f3..5598593 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -4,15 +4,14 @@ import Bubble, { BubbleOption } from "../../components/Bubble"; import styles from "./filterPopup.module.scss"; import TitleComponent from "src/components/Title"; import { Button } from "@nutui/nutui-react-taro"; -import { useListStore } from "../../store/listStore"; const timeOptions: BubbleOption[] = [ - { id: 1, label: "晨间 6:00-10:00", value: "morning" }, - { id: 2, label: "上午 10:00-12:00", value: "forenoon" }, - { id: 3, label: "中午 12:00-14:00", value: "noon" }, - { id: 4, label: "下午 14:00-18:00", value: "afternoon" }, - { id: 5, label: "晚上 18:00-22:00", value: "evening" }, - { id: 6, label: "夜间 22:00-24:00", value: "night" }, + { id: 1, label: "晨间 6:00-10:00", value: "1" }, + { id: 2, label: "上午 10:00-12:00", value: "2" }, + { id: 3, label: "中午 12:00-14:00", value: "3" }, + { id: 4, label: "下午 14:00-18:00", value: "4" }, + { id: 5, label: "晚上 18:00-22:00", value: "5" }, + { id: 6, label: "夜间 22:00-24:00", value: "6" }, ]; const locationOptions: BubbleOption[] = [ @@ -24,11 +23,25 @@ const locationOptions: BubbleOption[] = [ interface IProps { onCancel: () => void; onConfirm: () => void; + onChange: (params: Record) => void; loading: boolean; + filterOptions: Record; + onClear: () => void; } const FilterPopup = (props: IProps) => { - const { loading, onCancel, onConfirm } = props; + const { loading, onCancel, onConfirm, onChange, filterOptions, onClear } = props; + console.log('===filterOptions', filterOptions) + + const handleFilterChange = (name, value) => { + onChange({ [name]: value }); + }; + + const handleClearFilter = () => { + onClear() + onCancel(); + } + return ( <> { destroyOnClose position="top" round - closeOnOverlayClick={false} + closeOnOverlayClick={true} >
{/* 时间气泡选项 */} {}} - onChange={(value) => {}} + value={filterOptions?.time} + onChange={handleFilterChange} layout="grid" size="small" columns={3} + name="time" /> {/* 范围选择 */} @@ -55,6 +69,9 @@ const FilterPopup = (props: IProps) => { max={5.0} step={0.5} className={styles.filterPopupRange} + onChange={handleFilterChange} + value={filterOptions?.ntrp} + name='ntrp' /> {/* 场次气泡选项 */} @@ -62,19 +79,25 @@ const FilterPopup = (props: IProps) => { {}} - onChange={(value) => {}} + value={filterOptions?.site} + onChange={handleFilterChange} layout="grid" size="small" columns={3} + name='site' />
{/* 按钮 */}
- -
diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index 525fddc..0914616 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -4,7 +4,7 @@ import Menu from "../../components/Menu"; import CityFilter from "../../components/CityFilter"; import SearchBar from "../../components/SearchBar"; import FilterPopup from "./FilterPopup"; -import styles from "./index.module.scss"; +import styles from "./index.module.scss"; import { useEffect } from "react"; import Taro from "@tarojs/taro"; import { useListStore } from "../../store/listStore"; @@ -20,6 +20,10 @@ const ListPage = () => { refreshMatches, clearError, updateState, + filterCount, + updateFilterOptions, // 更新筛选条件 + filterOptions, + clearFilterOptions, } = useListStore() || {}; useEffect(() => { @@ -147,6 +151,10 @@ const ListPage = () => { updateState({ isShowFilterPopup: !isShowFilterPopup }); }; + const updateFilterSelect = (params) => { + updateState(params); + }; + const cityOptions: BubbleOption[] = [ { id: 0, label: "全城", value: "0" }, { id: 1, label: "3km", value: "3" }, @@ -154,15 +162,23 @@ const ListPage = () => { { id: 3, label: "10km", value: "10" }, ]; - const options = [ + const options = [ { text: "默认排序", value: "a" }, { text: "好评排序", value: "b" }, { text: "销量排序", value: "c" }, ]; + const handleUpdateFilterOptions = (params: Record) => { + updateFilterOptions(params); + }; + return (
- + 0} + filterCount={filterCount} + /> {/* 综合筛选 */} {isShowFilterPopup && (
@@ -170,15 +186,28 @@ const ListPage = () => { loading={loading} onCancel={toggleShowPopup} onConfirm={toggleShowPopup} + onChange={handleUpdateFilterOptions} + filterOptions={filterOptions} + onClear={clearFilterOptions} />
)} {/* 筛选 */} -
+
{/* 全城筛选 */} - { }} wrapperClassName={styles.menuFilter} /> + {}} + wrapperClassName={styles.menuFilter} + /> {/* 智能排序 */} - { }} wrapperClassName={styles.menuFilter} /> + {}} + wrapperClassName={styles.menuFilter} + />
{/* 列表内容 */} diff --git a/src/static/list/icon-filter-selected.svg b/src/static/list/icon-filter-selected.svg new file mode 100644 index 0000000..4b51f02 --- /dev/null +++ b/src/static/list/icon-filter-selected.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/static/list/icon-search.svg b/src/static/list/icon-search.svg new file mode 100644 index 0000000..3ca4401 --- /dev/null +++ b/src/static/list/icon-search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/store/listStore.ts b/src/store/listStore.ts index 3a61d67..f0b2cd7 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -22,6 +22,16 @@ interface ListState { error: string | null lastRefreshTime: string | null isShowFilterPopup: boolean + filterOptions: IFilterOptions + filterCount: number +} + +interface IFilterOptions { + location: string + time: string + ntrp: [number, number] + site: string + wanfa: string } // Store Actions 接口 @@ -35,11 +45,21 @@ interface ListActions { refreshMatches: () => Promise clearError: () => void updateState: (payload: Record) => void + updateFilterOptions: (payload: Record) => void + clearFilterOptions: () => void } // 完整的 Store 类型 type TennisStore = ListState & ListActions +const defaultFilterOptions: IFilterOptions = { + location: '', // 位置 + time: '', // 时间 + ntrp: [1.0, 5.0], // NTRP 水平区间 + site: '', // 场地类型 + wanfa: '', // 玩法 +}; + // 创建 store export const useListStore = create()((set, get) => ({ // 初始状态 @@ -49,6 +69,10 @@ export const useListStore = create()((set, get) => ({ lastRefreshTime: null, // 是否展示综合筛选弹窗 isShowFilterPopup: false, + // 综合筛选项 + filterOptions: defaultFilterOptions, + // 综合筛选 选择的筛选数量 + filterCount: 0, // 获取比赛数据 fetchMatches: async (params) => { @@ -98,8 +122,30 @@ export const useListStore = create()((set, get) => ({ clearError: () => { set({ error: null }) }, + + // 更新综合筛选项 + updateFilterOptions: (payload: Record) => { + const preFilterOptions = get()?.filterOptions || {} + const filterOptions = { ...preFilterOptions, ...payload } + const filterCount = Object.values(filterOptions).filter(Boolean).length + set({ + filterOptions, + filterCount + }) + }, + + // 清空综合筛选选项 + clearFilterOptions: () => { + set({ + filterOptions: defaultFilterOptions, + filterCount: 0 + }) + }, + // 更新store数据 updateState: (payload: Record) => { + const state = get(); + console.log('Store: 更新数据:', state); set({ ...(payload || {}) }) From d84a67726fc4e72bf7c571453e7ad7e5749c1d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Sun, 24 Aug 2025 15:51:41 +0800 Subject: [PATCH 07/22] feat: detail suspend --- src/components/UploadCover/index.scss | 0 src/components/UploadCover/index.tsx | 24 ++++++++++++++++++++++ src/config/images.js | 2 ++ src/static/publishBall/icon-arrow-left.svg | 3 +++ src/static/publishBall/icon-logo-go!.svg | 5 +++++ 5 files changed, 34 insertions(+) create mode 100644 src/components/UploadCover/index.scss create mode 100644 src/components/UploadCover/index.tsx create mode 100644 src/static/publishBall/icon-arrow-left.svg create mode 100644 src/static/publishBall/icon-logo-go!.svg diff --git a/src/components/UploadCover/index.scss b/src/components/UploadCover/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/UploadCover/index.tsx b/src/components/UploadCover/index.tsx new file mode 100644 index 0000000..426b037 --- /dev/null +++ b/src/components/UploadCover/index.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react' +import { Popup } from "@nutui/nutui-react-taro"; + +import './index.scss' + +export default function UploadCover(props) { + const { value = [], onChange = () => {} } = props + const [visible, setVisible] = useState(false) + + return ( + <> + setVisible(false)} + round + closeable + > +
+
上传封面
+
+ + ); +}; + diff --git a/src/config/images.js b/src/config/images.js index ab20192..cfaa79d 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -8,4 +8,6 @@ export default { ICON_COST: require('@/static/publishBall/icon-cost.svg'), ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), + ICON_ARROW_LEFT: require('@/static/publishBall/icon-arrow-left.svg'), + ICON_LOGO_GO: require('@/static/publishBall/icon-logo-go.svg'), } \ No newline at end of file diff --git a/src/static/publishBall/icon-arrow-left.svg b/src/static/publishBall/icon-arrow-left.svg new file mode 100644 index 0000000..fca111b --- /dev/null +++ b/src/static/publishBall/icon-arrow-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/static/publishBall/icon-logo-go!.svg b/src/static/publishBall/icon-logo-go!.svg new file mode 100644 index 0000000..468b5ff --- /dev/null +++ b/src/static/publishBall/icon-logo-go!.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 64001496631049e2ed4b089e5066ad09c053cad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sun, 24 Aug 2025 16:10:00 +0800 Subject: [PATCH 08/22] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E8=8E=B7=E5=8F=96=E6=89=8B=E6=9C=BA=E5=8F=B7?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/login/index/index.tsx | 18 +++++++++++++++--- src/services/loginService.ts | 19 ++++++++----------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/pages/login/index/index.tsx b/src/pages/login/index/index.tsx index 09951c0..29facb3 100644 --- a/src/pages/login/index/index.tsx +++ b/src/pages/login/index/index.tsx @@ -10,7 +10,7 @@ const LoginPage: React.FC = () => { const [show_terms_layer, set_show_terms_layer] = useState(false); // 微信授权登录 - const handle_wechat_login = async () => { + const handle_wechat_login = async (e: any) => { if (!agree_terms) { set_show_terms_layer(true); Taro.showToast({ @@ -21,9 +21,20 @@ const LoginPage: React.FC = () => { return; } + // 检查是否获取到手机号 + if (!e.detail || !e.detail.code) { + Taro.showToast({ + title: '获取手机号失败,请重试', + icon: 'none', + duration: 2000 + }); + return; + } + set_is_loading(true); try { - const response = await wechat_auth_login(); + // 传递手机号code给登录服务 + const response = await wechat_auth_login(e.detail.code); if (response.success) { save_login_state(response.token!, response.user_info!); @@ -123,7 +134,8 @@ const LoginPage: React.FC = () => { {/* 微信快捷登录 */} -
- )} -
+
暂无比赛数据
+ +
+ )} + + ); }; diff --git a/src/static/list/icon-arrow-down-white.svg b/src/static/list/icon-arrow-down-white.svg new file mode 100644 index 0000000..31becd1 --- /dev/null +++ b/src/static/list/icon-arrow-down-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/list/icon-arrow-down.svg b/src/static/list/icon-arrow-down.svg new file mode 100644 index 0000000..1c6953d --- /dev/null +++ b/src/static/list/icon-arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/list/icon-list-right-arrow.svg b/src/static/list/icon-list-right-arrow.svg new file mode 100644 index 0000000..018fb2e --- /dev/null +++ b/src/static/list/icon-list-right-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/list/icon-menu-item-selected.svg b/src/static/list/icon-menu-item-selected.svg new file mode 100644 index 0000000..c79702f --- /dev/null +++ b/src/static/list/icon-menu-item-selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/list/icon-play.svg b/src/static/list/icon-play.svg new file mode 100644 index 0000000..715cd6b --- /dev/null +++ b/src/static/list/icon-play.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/static/list/icon-site.svg b/src/static/list/icon-site.svg new file mode 100644 index 0000000..8e28d73 --- /dev/null +++ b/src/static/list/icon-site.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/store/listStore.ts b/src/store/listStore.ts index f0b2cd7..9a5ec9a 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -1,53 +1,6 @@ import { create } from 'zustand' import { getTennisMatches } from '../services/listApi' - -// 网球比赛数据接口 -export interface TennisMatch { - id: string - title: string - dateTime: string - location: string - distance: string - registeredCount: number - maxCount: number - skillLevel: string - matchType: string - images: string[] -} - -// Store 状态接口 -interface ListState { - matches: TennisMatch[] - loading: boolean - error: string | null - lastRefreshTime: string | null - isShowFilterPopup: boolean - filterOptions: IFilterOptions - filterCount: number -} - -interface IFilterOptions { - location: string - time: string - ntrp: [number, number] - site: string - wanfa: string -} - -// Store Actions 接口 -interface ListActions { - fetchMatches: (params?: { - page?: number - pageSize?: number - location?: string - skillLevel?: string - }) => Promise - refreshMatches: () => Promise - clearError: () => void - updateState: (payload: Record) => void - updateFilterOptions: (payload: Record) => void - clearFilterOptions: () => void -} +import {ListActions, IFilterOptions, ListState } from '../../types/list/types' // 完整的 Store 类型 type TennisStore = ListState & ListActions @@ -60,6 +13,8 @@ const defaultFilterOptions: IFilterOptions = { wanfa: '', // 玩法 }; +const defaultDistance = 'all'; // 默认距离 + // 创建 store export const useListStore = create()((set, get) => ({ // 初始状态 @@ -73,6 +28,43 @@ export const useListStore = create()((set, get) => ({ filterOptions: defaultFilterOptions, // 综合筛选 选择的筛选数量 filterCount: 0, + // 距离筛选 + distance: defaultDistance, + // 快捷筛选 + quickFilter: 1, // 1: 默认 2: 好评 3: 销量 + // 距离筛选数据 + distanceData: [ + { id: 0, label: "全城", value: "全城" }, + { id: 1, label: "3km", value: "3km" }, + { id: 2, label: "5km", value: "5km" }, + { id: 3, label: "10km", value: "10km" }, + ], + // 快捷筛选数据 + quickFilterData:[ + { text: "默认排序", value: "0" }, + { text: "好评排序", value: "1" }, + { text: "销量排序", value: "2" }, + ], + // 距离筛选和快捷筛选 + distanceQuickFilter: { + distance: '全城', + quick: '0', + }, + // 时间气泡数据 + timeBubbleData: [ + { id: 1, label: "晨间 6:00-10:00", value: "1" }, + { id: 2, label: "上午 10:00-12:00", value: "2" }, + { id: 3, label: "中午 12:00-14:00", value: "3" }, + { id: 4, label: "下午 14:00-18:00", value: "4" }, + { id: 5, label: "晚上 18:00-22:00", value: "5" }, + { id: 6, label: "夜间 22:00-24:00", value: "6" }, + ], + // 场地类型数据 + locationOptions: [ + { id: 1, label: "室内", value: "1" }, + { id: 2, label: "室外", value: "2" }, + { id: 3, label: "半室外", value: "3" }, + ], // 获取比赛数据 fetchMatches: async (params) => { @@ -87,12 +79,12 @@ export const useListStore = create()((set, get) => ({ }) console.log('Store: 成功获取网球比赛数据:', matches.length, '条') } catch (error) { - const errorMessage = error instanceof Error ? error.message : '未知错误' - set({ - error: errorMessage, - loading: false - }) - console.error('Store: 获取网球比赛数据失败:', errorMessage) + // const errorMessage = error instanceof Error ? error.message : '未知错误' + // set({ + // error: errorMessage, + // loading: false + // }) + // console.error('Store: 获取网球比赛数据失败:', errorMessage) } }, @@ -109,12 +101,12 @@ export const useListStore = create()((set, get) => ({ }) console.log('Store: 成功刷新网球比赛数据:', matches.length, '条') } catch (error) { - const errorMessage = error instanceof Error ? error.message : '未知错误' - set({ - error: errorMessage, - loading: false - }) - console.error('Store: 刷新网球比赛数据失败:', errorMessage) + // const errorMessage = error instanceof Error ? error.message : '未知错误' + // set({ + // error: errorMessage, + // loading: false + // }) + // console.error('Store: 刷新网球比赛数据失败:', errorMessage) } }, diff --git a/types/list/types.ts b/types/list/types.ts new file mode 100644 index 0000000..2fc8d71 --- /dev/null +++ b/types/list/types.ts @@ -0,0 +1,141 @@ +// 网球比赛数据接口 +export interface TennisMatch { + id: string + title: string + dateTime: string + location: string + distance: string + registeredCount: number + maxCount: number + skillLevel: string + matchType: string + images: string[] +} +export interface IFilterOptions { + location: string + time: string + ntrp: [number, number] + site: string + wanfa: string +} +export interface ListState { + matches: TennisMatch[] + loading: boolean + error: string | null + lastRefreshTime: string | null + isShowFilterPopup: boolean + filterOptions: IFilterOptions + filterCount: number + distance: string | number + quickFilter: string | number + distanceData: any[] + quickFilterData: any[] + distanceQuickFilter: { + distance: string + quick: string + } + timeBubbleData: BubbleOption[] + locationOptions: BubbleOption[] +} + +export interface ListState { + matches: TennisMatch[] + loading: boolean + error: string | null + lastRefreshTime: string | null + isShowFilterPopup: boolean + filterOptions: IFilterOptions + filterCount: number + distance: string | number + quickFilter: string | number + distanceData: any[] + quickFilterData: any[] + distanceQuickFilter: { + distance: string + quick: string + } +} + +export interface ListActions { + fetchMatches: (params?: { + page?: number + pageSize?: number + location?: string + skillLevel?: string + }) => Promise + refreshMatches: () => Promise + clearError: () => void + updateState: (payload: Record) => void + updateFilterOptions: (payload: Record) => void + clearFilterOptions: () => void +} + +// 快捷筛选 +export interface MenuFilterProps { + options: { text: string; value: string }[]; + value: string; + onChange: (name: string, value: string) => void; + wrapperClassName?: string; + itemClassName?: string; + name: string; +} + +// 距离筛选 +export interface DistanceFilterProps { + options: BubbleOption[]; + value: string; + onChange: (name: string, value: string) => void; + wrapperClassName?: string; + itemClassName?: string; + name: string; +} + +// bubble 组件 +export interface BubbleOption { + id: string | number; + label: string; + value: string | number; + disabled?: boolean; + icon?: React.ReactNode; + description?: string; +} + +export interface BubbleProps { + options: BubbleOption[]; + value?: string | number | (string | number)[]; + onChange?: ( + name: string, + value: string | number | (string | number)[], + option: BubbleOption | BubbleOption[] + ) => void; + multiple?: boolean; + layout?: "horizontal" | "vertical" | "grid"; + columns?: number; + size?: "small" | "medium" | "large"; + className?: string; + itemClassName?: string; + style?: React.CSSProperties; + disabled?: boolean; + name: string; +} + +export interface BubbleItemProps { + option: BubbleOption; + isSelected: boolean; + size: 'small' | 'medium' | 'large'; + disabled: boolean; + onClick: (option: BubbleOption) => void; + itemClassName?: string; +} + +// FilterPopup 组件 +export interface FilterPopupProps { + onCancel: () => void; + onConfirm: () => void; + onChange: (params: Record) => void; + loading: boolean; + filterOptions: Record; + onClear: () => void; + visible: boolean; + onClose: () => void; +} \ No newline at end of file From f4576bb099bca4c829fe48e623478de954368524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sun, 24 Aug 2025 16:27:34 +0800 Subject: [PATCH 10/22] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E5=AD=97?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/login/index/index.scss | 2 +- src/pages/login/terms/index.scss | 11 ++++++++++- src/pages/login/terms/index.tsx | 15 ++++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/pages/login/index/index.scss b/src/pages/login/index/index.scss index 6436bd6..6f2f2f0 100644 --- a/src/pages/login/index/index.scss +++ b/src/pages/login/index/index.scss @@ -370,7 +370,7 @@ font-family: 'PingFang SC'; font-weight: 400; font-size: 16px; - color: #07C160; + color: #000000; text-decoration: underline; cursor: pointer; transition: all 0.3s ease; diff --git a/src/pages/login/terms/index.scss b/src/pages/login/terms/index.scss index 61ac7c9..d163398 100644 --- a/src/pages/login/terms/index.scss +++ b/src/pages/login/terms/index.scss @@ -157,7 +157,7 @@ position: relative; z-index: 5; flex: 1; - padding: 0px 24px ; + box-sizing: border-box; overflow-y: auto; @@ -181,6 +181,7 @@ line-height: 1.43em; color: #000000; margin-bottom: 24px; + } // 条款详细内容 @@ -192,6 +193,14 @@ color: #000000; margin-bottom: 40px; white-space: pre-line; + padding: 0px 24px ; + + .terms_first_line, + span.terms_first_line { + font-weight: 500; + display: block; + margin-bottom: 16px; + } } // 底部按钮 diff --git a/src/pages/login/terms/index.tsx b/src/pages/login/terms/index.tsx index f5b94b5..591d437 100644 --- a/src/pages/login/terms/index.tsx +++ b/src/pages/login/terms/index.tsx @@ -24,7 +24,7 @@ const TermsPage: React.FC = () => { case 'terms': setPageTitle('条款和条件'); setTermsTitle('《开场的条款和条件》'); - setTermsContent(`欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。 + setTermsContent(`欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。 一、服务内容 1. 本平台为用户提供活动发布、报名、聊天室沟通、活动提醒等服务。 @@ -70,7 +70,7 @@ const TermsPage: React.FC = () => { case 'binding': setPageTitle('微信号绑定协议'); setTermsTitle('《开场与微信号绑定协议》'); - setTermsContent(`欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。 + setTermsContent(`欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。 一、绑定服务说明 1. 本平台提供微信账号绑定服务,用户可通过微信快捷登录方式使用平台功能。 @@ -115,7 +115,7 @@ const TermsPage: React.FC = () => { case 'privacy': setPageTitle('隐私权政策'); setTermsTitle('《隐私权政策》'); - setTermsContent(`本平台(以下简称"我们")非常重视用户的隐私保护,本隐私权政策说明了我们如何收集、使用、存储和保护您的个人信息。 + setTermsContent(`本平台(以下简称"我们")非常重视用户的隐私保护,本隐私权政策说明了我们如何收集、使用、存储和保护您的个人信息。 一、信息收集 1. 注册信息:包括手机号码、微信账号、昵称、头像等基本信息。 @@ -190,13 +190,14 @@ const TermsPage: React.FC = () => { {/* 条款详细内容 */} - - {termsContent} - + - + ); }; From 58bacb3a47c750779d4f2adfc4d183401be15c04 Mon Sep 17 00:00:00 2001 From: juguohong Date: Sun, 24 Aug 2025 19:58:00 +0800 Subject: [PATCH 11/22] =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=AA=A8=E6=9E=B6?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/index.ts | 2 +- src/app.config.ts | 12 +- src/components/GamePlayType/index.module.scss | 0 src/components/GamePlayType/index.tsx | 28 ++ .../{ListItem => ListCard}/index.scss | 28 +- .../{ListItem => ListCard}/index.tsx | 64 ++--- src/components/ListCardSkeleton/index.scss | 264 ++++++++++++++++++ src/components/ListCardSkeleton/index.tsx | 57 ++++ src/pages/list/FilterPopup.tsx | 9 +- src/pages/list/index.tsx | 59 +--- src/services/listApi.ts | 227 ++++++--------- src/store/listStore.ts | 15 +- types/list/types.ts | 16 ++ 13 files changed, 532 insertions(+), 249 deletions(-) create mode 100644 src/components/GamePlayType/index.module.scss create mode 100644 src/components/GamePlayType/index.tsx rename src/components/{ListItem => ListCard}/index.scss (88%) rename src/components/{ListItem => ListCard}/index.tsx (66%) create mode 100644 src/components/ListCardSkeleton/index.scss create mode 100644 src/components/ListCardSkeleton/index.tsx diff --git a/config/index.ts b/config/index.ts index c77a458..9e16ab3 100644 --- a/config/index.ts +++ b/config/index.ts @@ -2,7 +2,7 @@ import { defineConfig, type UserConfigExport } from '@tarojs/cli' import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin' import devConfig from './dev' import prodConfig from './prod' -import vitePluginImp from 'vite-plugin-imp' +// import vitePluginImp from 'vite-plugin-imp' import path from 'path' // https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数 diff --git a/src/app.config.ts b/src/app.config.ts index 7c74bbe..7b1fcad 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,12 +1,12 @@ export default defineAppConfig({ pages: [ - 'pages/login/index/index', - 'pages/login/verification/index', - 'pages/login/terms/index', - // 'pages/publishBall/index', + 'pages/list/index', + 'pages/publishBall/index', + // 'pages/login/index/index', + // 'pages/login/verification/index', + // 'pages/login/terms/index', // 'pages/mapDisplay/index', - // 'pages/list/index', - 'pages/index/index' + // 'pages/index/index' ], window: { backgroundTextStyle: 'light', diff --git a/src/components/GamePlayType/index.module.scss b/src/components/GamePlayType/index.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/GamePlayType/index.tsx b/src/components/GamePlayType/index.tsx new file mode 100644 index 0000000..d426d8b --- /dev/null +++ b/src/components/GamePlayType/index.tsx @@ -0,0 +1,28 @@ +import PopupGameplay from "../../pages/publishBall/components/PopupGameplay"; +import { View, Text, Image } from "@tarojs/components"; +import TitleComponent from "@/components/Title"; +import img from "@/config/images"; + +const GamePlayType = () => { + return ( + + } /> + { + console.log("onClose"); + }} + onConfirm={() => { + console.log("onConfirm"); + }} + visible={false} + options={[ + { label: "不限", value: "不限" }, + { label: "单打", value: "单打" }, + { label: "双打", value: "双打" }, + { label: "拉球", value: "拉球" }, + ]} + /> + + ); +}; +export default GamePlayType; diff --git a/src/components/ListItem/index.scss b/src/components/ListCard/index.scss similarity index 88% rename from src/components/ListItem/index.scss rename to src/components/ListCard/index.scss index 3fc1920..23ed8af 100644 --- a/src/components/ListItem/index.scss +++ b/src/components/ListCard/index.scss @@ -1,16 +1,17 @@ .list-item { display: flex; - padding: 16px; + padding: 12px 15px; background: #ffffff; border-radius: 20px; border: 0.5px solid #f0f0f0; + justify-content: space-between; } .content { flex: 1; display: flex; flex-direction: column; - gap: 6px; + width: calc(100% - 122px); } .titleWrapper { @@ -23,19 +24,40 @@ font-weight: 600; color: #000000; line-height: 24px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .title-right-arrow { width: 16px; height: 16px; + flex-shrink: 0; } -.date-time, .location { + display: flex; + align-items: center; +} + +.location-position { + max-width: 66%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.location-text { + display: block; +} + +.date-time { font-size: 12px; color: #3C3C4399; font-weight: 400; line-height: 18px; + margin-top: 6px; + margin-bottom: 4px; } .bottom-info { diff --git a/src/components/ListItem/index.tsx b/src/components/ListCard/index.tsx similarity index 66% rename from src/components/ListItem/index.tsx rename to src/components/ListCard/index.tsx index 46a6539..56bbc42 100644 --- a/src/components/ListItem/index.tsx +++ b/src/components/ListCard/index.tsx @@ -1,20 +1,10 @@ import { View, Text, Image } from "@tarojs/components"; import img from "../../config/images"; +import { ListCardProps } from "../../../types/list/types"; import "./index.scss"; +// import SkeletonComponent from "../../components/Skeleton"; -interface ListItemProps { - title: string; - dateTime: string; - location: string; - distance: string; - registeredCount: number; - maxCount: number; - skillLevel: string; - matchType: string; - images: string[]; -} - -const ListItem: React.FC = ({ +const ListCard: React.FC = ({ title, dateTime, location, @@ -24,7 +14,12 @@ const ListItem: React.FC = ({ skillLevel, matchType, images, + shinei, }) => { + const renderItemImage = (src: string) => { + return ; + }; + // 根据图片数量决定展示样式 const renderImages = () => { if (images.length === 0) return null; @@ -33,7 +28,8 @@ const ListItem: React.FC = ({ return ( - + {/* */} + {renderItemImage(images[0])} ); @@ -43,10 +39,12 @@ const ListItem: React.FC = ({ return ( - + {/* */} + {renderItemImage(images[0])} - + {/* */} + {renderItemImage(images[1])} ); @@ -55,19 +53,13 @@ const ListItem: React.FC = ({ // 3张或更多图片 return ( - - - - - - - - - + {renderItemImage(images[0])} + {renderItemImage(images[1])} + {renderItemImage(images[2])} ); }; - + console.log("===ttt", !title); return ( {/* 左侧内容区域 */} @@ -83,12 +75,20 @@ const ListItem: React.FC = ({ {/* 时间信息 */} - {dateTime} - {/* 地点和距离 */} - - {location}・{distance} - + + {dateTime} + + + {/* 地点,室内外,距离 */} + + + {location} + + {shinei && `・${shinei}`} + {distance && `・${distance}`} + + {/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */} @@ -131,4 +131,4 @@ const ListItem: React.FC = ({ ); }; -export default ListItem; +export default ListCard; diff --git a/src/components/ListCardSkeleton/index.scss b/src/components/ListCardSkeleton/index.scss new file mode 100644 index 0000000..e642055 --- /dev/null +++ b/src/components/ListCardSkeleton/index.scss @@ -0,0 +1,264 @@ +.list-item { + display: flex; + padding: 12px 15px; + background: #ffffff; + border-radius: 20px; + border: 0.5px solid #f0f0f0; + justify-content: space-between; + --nutui-skeleton-line-height: 24px; + --nutui-skeleton-line-border-radius: 24px; + + .nut-skeleton-block { + margin: 0; + } +} + +.content { + flex: 1; + display: flex; + flex-direction: column; + width: calc(100% - 122px); +} + +.titleWrapper { + display: flex; + align-items: center; +} + +.title { + font-size: 16px; + font-weight: 600; + color: #000000; + line-height: 24px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.title-right-arrow { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.location { + display: flex; + align-items: center; +} + +.location-position { + max-width: 66%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.location-text { + display: block; +} + +.date-time { + font-size: 12px; + color: #3C3C4399; + font-weight: 400; + line-height: 18px; + margin-top: 6px; + margin-bottom: 4px; +} + +.bottom-info { + display: flex; + align-items: center; + margin-top: 4px; + column-gap: 4px; +} + +.left-section { + display: flex; + align-items: center; + gap: 8px; +} + +.avatar-group { + display: flex; + align-items: center; +} + +.avatar { + width: 20px; + height: 20px; + border-radius: 50%; + background: #e0e0e0; + border: 2px solid #ffffff; + margin-left: -8px; + overflow: hidden; + box-sizing: border-box; + + .avatar-image { + width: 100%; + height: 100%; + object-fit: cover; + } + + &:first-child { + margin-left: 0; + z-index: 3; + } + + &:nth-child(2) { + z-index: 2; + } + + &:nth-child(3) { + z-index: 1; + } +} + +.registration-text { + font-size: 12px; + color: #999999; +} + +.tags { + display: flex; + gap: 4px; +} + +.tag { + box-sizing: border-box; + padding: 0 6px; + border: 0.5px solid #00000029; + height: 20px; + border-radius: 20px; + min-width: 38px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: #000000; + font-size: 11px; +} + +.tag-text-max { + color: #666666; +} + +.image-section { + width: 100px; + height: 100px; + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-basis: 100px; + flex-grow: 0; + flex-shrink: 0; + + .image-container { + width: 100%; + height: 100%; + border: 1.5px solid #ffffff; + border-radius: 10px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); + overflow: hidden; + position: absolute; + box-sizing: border-box; + + .nut-skeleton, + .nut-skeleton-content, + .nut-skeleton-block { + width: 100%; + height: 100%; + } + + .nut-skeleton-block { + margin: 0; + border-radius: unset; + } + + .image { + border-radius: 10px; + } + } +} + +.single-image { + position: relative; + width: 88px; + height: 88px; + + .image-container { + width: 88px; + height: 88px; + transform: rotate(-10deg); + } +} + +.double-image { + width: 100%; + height: 100%; + position: relative; + + .image-container { + width: 60%; + height: 60%; + position: absolute; + overflow: hidden; + top: 20%; + + &:first-child { + z-index: 2; + transform: translateX(4px) rotate(-10deg); + } + + &:last-child { + right: 0; + z-index: 1; + transform: translateX(-4px) rotate(10deg); + } + } +} + +.triple-image { + width: 100%; + height: 100%; + position: relative; + + .image-container { + position: absolute; + overflow: hidden; + + &:nth-child(1) { + bottom: 0; + left: 0; + width: 55px; + height: 55px; + z-index: 3; + transform: translateX(4px) rotate(-10deg); + } + + &:nth-child(2) { + bottom: 10px; + right: 0; + width: 55px; + height: 55px; + z-index: 2; + transform: rotate(3deg); + } + + &:nth-child(3) { + top: 5%; + left: 50%; + width: 100rpx; + height: 100rpx; + z-index: 1; + transform: translateX(-50%); + } + } +} + +.image { + width: 100%; + height: 100%; + object-fit: cover; +} \ No newline at end of file diff --git a/src/components/ListCardSkeleton/index.tsx b/src/components/ListCardSkeleton/index.tsx new file mode 100644 index 0000000..d19bf56 --- /dev/null +++ b/src/components/ListCardSkeleton/index.tsx @@ -0,0 +1,57 @@ +import { View } from "@tarojs/components"; +import { Skeleton } from "@nutui/nutui-react-taro"; +import "./index.scss"; + +const ListCard = () => { + return ( + + {/* 左侧内容区域 */} + + {/* 标题 */} + + + + + {/* 时间信息 */} + + + + + + {/* 地点,室内外,距离 */} + + + + + + {/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */} + + + + {Array.from({ length: 3 }).map((_, index) => ( + + + + ))} + + + + + + + + + + {/* 右侧图片区域 */} + + + + + + + + + ); +}; + +export default ListCard; diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index 9af9136..8beea3a 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -2,12 +2,13 @@ import { Popup } from "@nutui/nutui-react-taro"; import Range from "../../components/Range"; import Bubble from "../../components/Bubble"; import styles from "./filterPopup.module.scss"; -import TitleComponent from "src/components/Title"; +import TitleComponent from "@/components/Title"; import { Button } from "@nutui/nutui-react-taro"; import { Image } from "@tarojs/components"; import img from "../../config/images"; import { useListStore } from "src/store/listStore"; -import {FilterPopupProps} from '../../../types/list/types' +import { FilterPopupProps } from "../../../types/list/types"; +import GamePlayType from "@/components/GamePlayType"; const FilterPopup = (props: FilterPopupProps) => { const { @@ -20,7 +21,7 @@ const FilterPopup = (props: FilterPopupProps) => { visible, onClose, } = props; - + const store = useListStore() || {}; const { timeBubbleData, locationOptions } = store; @@ -82,6 +83,8 @@ const FilterPopup = (props: FilterPopupProps) => { name="site" />
+ {/* 玩法 */} + {/* 按钮 */}
-
- )} + {loading && + new Array(10).fill(0).map(() => { + return ; + })} ); diff --git a/src/services/listApi.ts b/src/services/listApi.ts index a937558..77ebd65 100644 --- a/src/services/listApi.ts +++ b/src/services/listApi.ts @@ -1,93 +1,85 @@ -import { TennisMatch } from '../store/listStore' +import { TennisMatch } from "../store/listStore"; // 模拟网络延迟 -const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); // 模拟API响应格式 interface ApiResponse { - code: number - message: string - data: T - timestamp: number + code: number; + message: string; + data: T; + timestamp: number; } - // 模拟网球比赛数据 const mockTennisMatches: TennisMatch[] = [ { - id: '1', - title: '周一晚场浦东新区单打约球', - dateTime: '明天(周五)下午5点 2小时', - location: '仁恒河滨花园网球场・室外', - distance: '3.5km', + id: "1", + title: "周一晚场浦东新区单打约球", + dateTime: "明天(周五)下午5点 2小时", + location: "仁恒河滨花园网球场", + distance: "3.5km", + shinei: "室内", registeredCount: 3, maxCount: 4, - skillLevel: '2.0 至 2.5', - matchType: '双打', + skillLevel: "2.0 至 2.5", + matchType: "双打", images: [ - 'https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center', - 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=200&h=200&fit=crop&crop=center', - 'https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=200&h=200&fit=crop&crop=center' - ] + "https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center", + "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=200&h=200&fit=crop&crop=center", + "https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=200&h=200&fit=crop&crop=center", + ], }, { - id: '2', - title: '浦东新区单打约球', - dateTime: '明天(周五)下午5点 2小时', - location: '仁恒河滨花园网球场・室外', - distance: '3.5km', + id: "2", + title: "浦东新区单打约球", + dateTime: "明天(周五)下午5点 2小时", + location: "仁恒河滨花园网球场", + distance: "3.5km", + shinei: "室外", registeredCount: 2, maxCount: 4, - skillLevel: '2.0 至 2.5', - matchType: '双打', + skillLevel: "2.0 至 2.5", + matchType: "双打", images: [ - 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=200&h=200&fit=crop&crop=center', - 'https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=200&h=200&fit=crop&crop=center' - ] + "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=200&h=200&fit=crop&crop=center", + "https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=200&h=200&fit=crop&crop=center", + ], }, { - id: '3', - title: '黄浦区双打约球', - dateTime: '7月20日(周日)下午6点 2小时', - location: '仁恒河滨花园网球场・室外', - distance: '3.5km', + id: "3", + title: "黄浦区双打约球", + dateTime: "7月20日(周日)下午6点 2小时", + location: "仁恒河滨花园网球场", + distance: "3.5km", registeredCount: 3, maxCount: 4, - skillLevel: '2.0 至 2.5', - matchType: '双打', + skillLevel: "2.0 至 2.5", + matchType: "双打", images: [ - 'https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center' - ] - } -] + "https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center", + ], + }, +]; // 模拟数据变化 const generateDynamicData = (): TennisMatch[] => { - return mockTennisMatches.map(match => ({ + Promise.resolve((res) => { + setTimeout(res, 3000); + }); + return mockTennisMatches.map((match) => ({ ...match, // 随机更新注册人数 registeredCount: Math.min( - match.maxCount, + match.maxCount, Math.max(0, match.registeredCount + Math.floor(Math.random() * 3) - 1) ), // 随机更新距离 distance: `${(Math.random() * 5 + 1).toFixed(1)}km`, // 随机更新时间 - dateTime: Math.random() > 0.5 ? match.dateTime : '今天下午3点 2小时' - })) -} - -// 模拟网络错误 -const simulateNetworkError = (): boolean => { - // 10% 概率模拟网络错误 - return Math.random() < 0.1 -} - -// 模拟网络超时 -const simulateTimeout = (): boolean => { - // 5% 概率模拟超时 - return Math.random() < 0.05 -} + dateTime: Math.random() > 0.5 ? match.dateTime : "今天下午3点 2小时", + })); +}; /** * 获取网球比赛列表 @@ -95,59 +87,21 @@ const simulateTimeout = (): boolean => { * @returns Promise */ export const getTennisMatches = async (params?: { - page?: number - pageSize?: number - location?: string - skillLevel?: string + page?: number; + pageSize?: number; + location?: string; + skillLevel?: string; }): Promise => { try { - console.log('API调用: getTennisMatches', params) - - // 模拟网络延迟 (800-1500ms) - const delayTime = 800 + Math.random() * 700 - await delay(delayTime) - - // 模拟网络错误 - if (simulateNetworkError()) { - throw new Error('网络连接失败,请检查网络设置') - } - - // 模拟超时 - if (simulateTimeout()) { - throw new Error('请求超时,请稍后重试') - } - // 生成动态数据 - const matches = generateDynamicData() - - // 模拟分页 - if (params?.page && params?.pageSize) { - const start = (params.page - 1) * params.pageSize - const end = start + params.pageSize - return matches.slice(start, end) - } - - // 模拟筛选 - if (params?.location) { - return matches.filter(match => - match.location.includes(params.location!) - ) - } - - if (params?.skillLevel) { - return matches.filter(match => - match.skillLevel.includes(params.skillLevel!) - ) - } - - console.log('API响应成功:', matches.length, '条数据') - return matches - + const matches = generateDynamicData(); + + return matches; } catch (error) { - console.error('API调用失败:', error) - throw error + console.error("API调用失败:", error); + throw error; } -} +}; /** * 刷新网球比赛数据 @@ -155,60 +109,47 @@ export const getTennisMatches = async (params?: { */ export const refreshTennisMatches = async (): Promise => { try { - console.log('API调用: refreshTennisMatches') - - // 模拟刷新延迟 (500-1000ms) - const delayTime = 500 + Math.random() * 500 - await delay(delayTime) - - // 模拟网络错误 - if (simulateNetworkError()) { - throw new Error('刷新失败,请稍后重试') - } - // 生成新的动态数据 - const matches = generateDynamicData() - - console.log('API刷新成功:', matches.length, '条数据') - return matches - + const matches = generateDynamicData(); + return matches; } catch (error) { - console.error('API刷新失败:', error) - throw error + console.error("API刷新失败:", error); + throw error; } -} +}; /** * 获取比赛详情 * @param id 比赛ID * @returns Promise */ -export const getTennisMatchDetail = async (id: string): Promise => { +export const getTennisMatchDetail = async ( + id: string +): Promise => { try { - console.log('API调用: getTennisMatchDetail', id) - + console.log("API调用: getTennisMatchDetail", id); + // 模拟网络延迟 - await delay(600 + Math.random() * 400) - + await delay(600 + Math.random() * 400); + // 模拟网络错误 if (simulateNetworkError()) { - throw new Error('获取详情失败,请稍后重试') + throw new Error("获取详情失败,请稍后重试"); } - - const match = mockTennisMatches.find(m => m.id === id) - + + const match = mockTennisMatches.find((m) => m.id === id); + if (!match) { - throw new Error('比赛不存在') + throw new Error("比赛不存在"); } - - console.log('API获取详情成功:', match.title) - return match - + + console.log("API获取详情成功:", match.title); + return match; } catch (error) { - console.error('API获取详情失败:', error) - throw error + console.error("API获取详情失败:", error); + throw error; } -} +}; /** * 模拟API统计信息 @@ -218,6 +159,6 @@ export const getApiStats = () => { totalCalls: 0, successRate: 0.95, averageResponseTime: 800, - lastCallTime: new Date().toISOString() - } -} + lastCallTime: new Date().toISOString(), + }; +}; diff --git a/src/store/listStore.ts b/src/store/listStore.ts index 9a5ec9a..b5eb210 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -77,14 +77,9 @@ export const useListStore = create()((set, get) => ({ loading: false, lastRefreshTime: new Date().toISOString() }) - console.log('Store: 成功获取网球比赛数据:', matches.length, '条') + } catch (error) { - // const errorMessage = error instanceof Error ? error.message : '未知错误' - // set({ - // error: errorMessage, - // loading: false - // }) - // console.error('Store: 获取网球比赛数据失败:', errorMessage) + } }, @@ -101,12 +96,6 @@ export const useListStore = create()((set, get) => ({ }) console.log('Store: 成功刷新网球比赛数据:', matches.length, '条') } catch (error) { - // const errorMessage = error instanceof Error ? error.message : '未知错误' - // set({ - // error: errorMessage, - // loading: false - // }) - // console.error('Store: 刷新网球比赛数据失败:', errorMessage) } }, diff --git a/types/list/types.ts b/types/list/types.ts index 2fc8d71..789164c 100644 --- a/types/list/types.ts +++ b/types/list/types.ts @@ -10,6 +10,7 @@ export interface TennisMatch { skillLevel: string matchType: string images: string[] + shinei: string } export interface IFilterOptions { location: string @@ -138,4 +139,19 @@ export interface FilterPopupProps { onClear: () => void; visible: boolean; onClose: () => void; +} + +// 列表卡片 +export interface ListCardProps { + title: string; + dateTime: string; + location: string; + distance: string; + registeredCount: number; + maxCount: number; + skillLevel: string; + matchType: string; + images: string[]; + shinei: string; + showSkeleton?: boolean; } \ No newline at end of file From 517c61d691618906963767a24c7f2bf24a19b9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sun, 24 Aug 2025 20:55:38 +0800 Subject: [PATCH 12/22] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.config.ts | 2 + src/pages/userInfo/myself/index.config.ts | 9 + src/pages/userInfo/myself/index.scss | 600 ++++++++++++++++++++++ src/pages/userInfo/myself/index.tsx | 377 ++++++++++++++ src/static/userInfo/default_avatar.svg | 5 + src/static/userInfo/game1.svg | 6 + src/static/userInfo/game2.svg | 6 + src/static/userInfo/game3.svg | 6 + src/static/userInfo/location.svg | 5 + src/static/userInfo/message.svg | 6 + src/static/userInfo/plus.svg | 4 + src/static/userInfo/tennis.svg | 7 + src/static/userInfo/user1.svg | 5 + src/static/userInfo/user2.svg | 5 + 14 files changed, 1043 insertions(+) create mode 100644 src/pages/userInfo/myself/index.config.ts create mode 100644 src/pages/userInfo/myself/index.scss create mode 100644 src/pages/userInfo/myself/index.tsx create mode 100644 src/static/userInfo/default_avatar.svg create mode 100644 src/static/userInfo/game1.svg create mode 100644 src/static/userInfo/game2.svg create mode 100644 src/static/userInfo/game3.svg create mode 100644 src/static/userInfo/location.svg create mode 100644 src/static/userInfo/message.svg create mode 100644 src/static/userInfo/plus.svg create mode 100644 src/static/userInfo/tennis.svg create mode 100644 src/static/userInfo/user1.svg create mode 100644 src/static/userInfo/user2.svg diff --git a/src/app.config.ts b/src/app.config.ts index 7c74bbe..0b0c69a 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -3,6 +3,8 @@ export default defineAppConfig({ 'pages/login/index/index', 'pages/login/verification/index', 'pages/login/terms/index', + 'pages/userInfo/myself/index', + // 'pages/publishBall/index', // 'pages/mapDisplay/index', // 'pages/list/index', diff --git a/src/pages/userInfo/myself/index.config.ts b/src/pages/userInfo/myself/index.config.ts new file mode 100644 index 0000000..eb44728 --- /dev/null +++ b/src/pages/userInfo/myself/index.config.ts @@ -0,0 +1,9 @@ +export default { + navigationBarTitleText: '个人主页', + navigationBarBackgroundColor: '#FFFFFF', + navigationBarTextStyle: 'black', + backgroundColor: '#FAFAFA', + enablePullDownRefresh: false, + disableScroll: false, + navigationStyle: 'custom' +} \ No newline at end of file diff --git a/src/pages/userInfo/myself/index.scss b/src/pages/userInfo/myself/index.scss new file mode 100644 index 0000000..2aa9ace --- /dev/null +++ b/src/pages/userInfo/myself/index.scss @@ -0,0 +1,600 @@ +// 个人页面样式 +.myself_page { + min-height: 100vh; + background: radial-gradient(circle at 50% 0%, rgba(238, 255, 220, 1) 0%, rgba(255, 255, 255, 1) 37%); + position: relative; + overflow: hidden; + box-sizing: border-box; +} + +// 主要内容区域 +.main_content { + position: relative; + z-index: 5; + flex: 1; + margin-top: 0; + box-sizing: border-box; + overflow-y: auto; + padding: 15px 15px 15px; + + // 用户信息区域 + .user_info_section { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 16px; + + // 基本信息 + .basic_info { + display: flex; + align-items: center; + gap: 16px; + + .avatar_container { + width: 64px; + height: 64px; + border-radius: 50%; + overflow: hidden; + box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2); + + .avatar { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + .info_container { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + + .nickname { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 20px; + line-height: 1.4em; + letter-spacing: 1.9%; + color: #000000; + } + + .join_date { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.4em; + letter-spacing: 2.7%; + color: rgba(0, 0, 0, 0.35); + } + } + } + + // 统计数据 + .stats_section { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24px; + + .stats_container { + display: flex; + align-items: center; + gap: 20px; + + .stat_item { + display: flex; + flex-direction: column; + align-items: center; + + .stat_number { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 18px; + line-height: 1.4em; + letter-spacing: 2.1%; + color: rgba(0, 0, 0, 0.85); + } + + .stat_label { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 12px; + line-height: 1.4em; + letter-spacing: 3.2%; + color: rgba(0, 0, 0, 0.35); + } + } + } + + .action_buttons { + display: flex; + align-items: center; + gap: 12px; + + + + .follow_button { + display: flex; + align-items: center; + gap: 4px; + padding: 12px 16px 12px 12px; + height: 40px; + background: #000000; + border: 0.5px solid rgba(0, 0, 0, 0.06); + border-radius: 999px; + cursor: pointer; + transition: all 0.3s ease; + + &.following { + background: #FFFFFF; + color: #000000; + } + + .button_icon { + width: 20px; + height: 20px; + } + + .button_text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.4em; + color: #FFFFFF; + + .following & { + color: #000000; + } + } + } + + .message_button { + width: 40px; + height: 40px; + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.12); + border-radius: 999px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + + .button_icon { + width: 18px; + height: 18px; + } + } + + .edit_button { + min-width: 60px; + height: 40px; + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.12); + border-radius: 999px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + padding: 0 12px; + + .button_text { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 14px; + line-height: 1.4em; + color: #000000; + } + } + + .share_button { + min-width: 60px; + height: 40px; + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.12); + border-radius: 999px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + padding: 0 12px; + margin: 0px !important; + + .button_text { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 14px; + line-height: 1.4em; + color: #000000; + } + } + } + } + + // 标签和简介 + .tags_bio_section { + display: flex; + flex-direction: column; + gap: 10px; + + .tags_container { + display: flex; + gap: 8px; + flex-wrap: wrap; + + .tag_item { + display: flex; + align-items: center; + gap: 4px; + padding: 6px 8px; + height: 20px; + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.16); + border-radius: 999px; + + .tag_icon { + width: 12px; + height: 12px; + } + + .tag_text { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 11px; + line-height: 1.8em; + letter-spacing: -2.1%; + color: #000000; + } + } + } + + .bio_text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.571em; + color: rgba(0, 0, 0, 0.65); + white-space: pre-line; + } + } + + // 球局订单和收藏功能 + .quick_actions_section { + margin-bottom: 16px; + + .action_card { + display: flex; + align-items: center; + background: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.06); + border-radius: 12px; + box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06); + overflow: hidden; + + .action_content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 20px 0; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.02); + } + + .action_icon { + width: 20px; + height: 20px; + } + + .action_text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 15px; + line-height: 1.4em; + color: #000000; + } + } + + .action_divider { + width: 1px; + height: 16px; + background: rgba(0, 0, 0, 0.06); + } + } + } + } + + // 球局类型标签页 + .game_tabs_section { + margin-bottom: 16px; + + .tab_container { + display: flex; + gap: 16px; + padding: 12px 15px; + + .tab_item { + padding: 12px 0; + cursor: pointer; + transition: all 0.3s ease; + + .tab_text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 20px; + line-height: 1.4em; + letter-spacing: 1.9%; + color: rgba(0, 0, 0, 0.85); + transition: color 0.3s ease; + } + + &.active { + .tab_text { + color: #000000; + } + } + + &:not(.active) { + .tab_text { + color: rgba(0, 0, 0, 0.2); + } + } + } + } + } + + // 球局列表区域 + .game_list_section { + .date_header { + display: flex; + align-items: center; + gap: 4px; + padding: 10px 15px; + margin-bottom: 16px; + + .date_text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.4em; + letter-spacing: 2.71%; + color: rgba(0, 0, 0, 0.85); + } + + .separator { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 18px; + line-height: 1.4em; + letter-spacing: 2.11%; + color: rgba(0, 0, 0, 0.35); + } + + .weekday_text { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.4em; + letter-spacing: 2.71%; + color: rgba(0, 0, 0, 0.85); + } + } + + // 球局卡片 + .game_cards { + display: flex; + flex-direction: column; + gap: 5px; + padding: 0 5px 15px; + + .game_card { + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.08); + border-radius: 20px; + padding: 0 0 12px; + box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06); + cursor: pointer; + transition: all 0.3s ease; + position: relative; + + &:active { + transform: scale(0.98); + } + + // 球局标题和类型 + .game_header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 15px 0; + + .game_title { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 16px; + line-height: 1.5em; + color: #000000; + } + + .game_type_icon { + width: 16px; + height: 16px; + + .type_icon { + width: 100%; + height: 100%; + } + } + } + + // 球局时间 + .game_time { + padding: 6px 15px 0; + + .time_text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.5em; + color: rgba(60, 60, 67, 0.6); + } + } + + // 球局地点和类型 + .game_location { + display: flex; + align-items: center; + gap: 2px; + padding: 4px 15px 0; + + .location_text, + .type_text, + .distance_text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 12px; + line-height: 1.5em; + color: rgba(60, 60, 67, 0.6); + } + + .separator { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.3em; + color: rgba(60, 60, 67, 0.3); + } + } + + // 球局图片 + .game_images { + position: absolute; + top: 11px; + right: 5px; + width: 100px; + height: 100px; + box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.2); + + .game_image { + position: absolute; + width: 56.44px; + height: 56.44px; + border-radius: 9px; + border: 1.5px solid #FFFFFF; + + &:nth-child(1) { + top: 4.18px; + left: 19.18px; + } + + &:nth-child(2) { + top: 26.5px; + left: 38px; + width: 61.86px; + height: 61.86px; + } + + &:nth-child(3) { + top: 32.5px; + left: 0; + width: 62.04px; + height: 62.04px; + } + } + } + + // 球局信息标签 + .game_tags { + display: flex; + flex-direction: row; + gap: 6px; + padding: 8px 15px 0; + + .participants_info { + display: flex; + gap: 4px; + + .avatars { + display: flex; + align-items: center; + gap: -8px; + + .participant_avatar { + width: 20px; + height: 20px; + border-radius: 50%; + border: 1px solid #FFFFFF; + } + } + + .participants_count { + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.16); + border-radius: 999px; + padding: 6px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + + .count_text { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 11px; + line-height: 1.8em; + letter-spacing: -2.1%; + color: #000000; + } + } + } + + .game_info_tags { + display: flex; + gap: 4px; + + .info_tag { + background: #FFFFFF; + border: 0.5px solid rgba(0, 0, 0, 0.16); + border-radius: 999px; + padding: 6px 8px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + + .tag_text { + font-family: 'PingFang SC'; + font-weight: 500; + font-size: 11px; + line-height: 1.8em; + letter-spacing: -2.1%; + color: #000000; + } + } + } + } + } + } + } +} + +// 底部指示器 +.home_indicator { + position: absolute; + bottom: 21px; + left: 50%; + transform: translateX(-50%); + width: 140px; + height: 5px; + background: #000000; + border-radius: 2.5px; + z-index: 10; +} \ No newline at end of file diff --git a/src/pages/userInfo/myself/index.tsx b/src/pages/userInfo/myself/index.tsx new file mode 100644 index 0000000..d4ca486 --- /dev/null +++ b/src/pages/userInfo/myself/index.tsx @@ -0,0 +1,377 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, Image, ScrollView, Button } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import './index.scss'; + +// 用户信息接口 +interface UserInfo { + id: string; + nickname: string; + avatar: string; + join_date: string; + stats: { + following: number; + friends: number; + hosted: number; + participated: number; + }; + tags: string[]; + bio: string; + location: string; + occupation: string; + ntrp_level: string; +} + +// 球局记录接口 +interface GameRecord { + id: string; + title: string; + date: string; + time: string; + duration: string; + location: string; + type: string; + distance: string; + participants: { + avatar: string; + nickname: string; + }[]; + max_participants: number; + current_participants: number; + level_range: string; + game_type: string; + images: string[]; +} + +const MyselfPage: React.FC = () => { + // 获取页面参数 + const instance = Taro.getCurrentInstance(); + const user_id = instance.router?.params?.userid; + + // 判断是否为当前用户 + const is_current_user = !user_id; + + // 模拟用户数据 + const [user_info] = useState({ + id: '1', + nickname: '188的王晨', + avatar: require('../../../static/userInfo/default_avatar.svg'), + join_date: '2025年9月加入', + stats: { + following: 124, + friends: 24, + hosted: 7, + participated: 24 + }, + tags: ['上海黄浦', '互联网从业者', 'NTRP 4.0'], + bio: '网球入坑两年,偏好双打,正手进攻型选手\n平时在张江、世纪公园附近活动,欢迎约球!\n不卷分数,但认真对待每一拍,每一场球都想打得开心。有时候也会带相机来拍点照片📸', + location: '上海黄浦', + occupation: '互联网从业者', + ntrp_level: 'NTRP 4.0' + }); + + // 模拟球局数据 + const [game_records] = useState([ + { + id: '1', + title: '女生轻松双打', + date: '明天(周五)', + time: '下午5点', + duration: '2小时', + location: '仁恒河滨花园网球场', + type: '室外', + distance: '3.5km', + participants: [ + { avatar: require('../../../static/userInfo/user1.svg'), nickname: '用户1' }, + { avatar: require('../../../static/userInfo/user2.svg'), nickname: '用户2' } + ], + max_participants: 4, + current_participants: 2, + level_range: '2.0 至 2.5', + game_type: '双打', + images: [ + require('../../../static/userInfo/game1.svg'), + require('../../../static/userInfo/game2.svg'), + require('../../../static/userInfo/game3.svg') + ] + } + ]); + + // 关注状态 + const [is_following, setIsFollowing] = useState(false); + + // 当前激活的标签页 + const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted'); + + // 处理关注/取消关注 + const handle_follow = () => { + setIsFollowing(!is_following); + Taro.showToast({ + title: is_following ? '已取消关注' : '关注成功', + icon: 'success', + duration: 1500 + }); + }; + + // 处理分享 + const handle_share = () => { + Taro.showShareMenu({ + withShareTicket: true + }); + }; + + // 处理返回 + const handle_back = () => { + Taro.navigateBack(); + }; + + // 处理编辑资料 + const handle_edit_profile = () => { + Taro.navigateTo({ + url: '/pages/userInfo/edit/index' + }); + }; + + // 处理球局详情 + const handle_game_detail = (game_id: string) => { + Taro.navigateTo({ + url: `/pages/game/detail/index?id=${game_id}` + }); + }; + + // 处理球局订单 + const handle_game_orders = () => { + Taro.navigateTo({ + url: '/pages/game/orders/index' + }); + }; + + // 处理收藏 + const handle_favorites = () => { + Taro.navigateTo({ + url: '/pages/game/favorites/index' + }); + }; + + return ( + + {/* 主要内容 */} + + {/* 用户信息区域 */} + + {/* 头像和基本信息 */} + + + + + + {user_info.nickname} + {user_info.join_date} + + + + {/* 统计数据 */} + + + + {user_info.stats.following} + 关注 + + + {user_info.stats.friends} + 球友 + + + {user_info.stats.hosted} + 主办 + + + {user_info.stats.participated} + 参加 + + + + {/* 只有非当前用户才显示关注按钮 */} + {!is_current_user && ( + + )} + {/* 只有非当前用户才显示消息按钮 */} + {!is_current_user && ( + + )} + {/* 只有当前用户才显示编辑按钮 */} + {is_current_user && ( + + )} + {/* 只有当前用户才显示分享按钮 */} + {is_current_user && ( + + )} + + + + {/* 标签和简介 */} + + + + + {user_info.location} + + + {user_info.occupation} + + + {user_info.ntrp_level} + + + {user_info.bio} + + + {/* 球局订单和收藏功能 */} + + + + + 球局订单 + + + + + 收藏 + + + + + + {/* 球局类型标签页 */} + + + setActiveTab('hosted')}> + 我主办的 + + setActiveTab('participated')}> + 我参与的 + + + + + {/* 球局列表 */} + + + 5月28日 + / + 星期三 + + + {/* 球局卡片 */} + + {game_records.map((game) => ( + handle_game_detail(game.id)} + > + {/* 球局标题和类型 */} + + {game.title} + + + + + + {/* 球局时间 */} + + + {game.date} {game.time} {game.duration} + + + + {/* 球局地点和类型 */} + + {game.location} + · + {game.type} + · + {game.distance} + + + {/* 球局图片 */} + + {game.images.map((image, index) => ( + + ))} + + + {/* 球局信息标签 */} + + + + {game.participants.map((participant, index) => ( + + ))} + + + + 报名人数 {game.current_participants}/{game.max_participants} + + + + + + {game.level_range} + + + {game.game_type} + + + + + ))} + + + + + ); +}; + +export default MyselfPage; diff --git a/src/static/userInfo/default_avatar.svg b/src/static/userInfo/default_avatar.svg new file mode 100644 index 0000000..5cf5917 --- /dev/null +++ b/src/static/userInfo/default_avatar.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/static/userInfo/game1.svg b/src/static/userInfo/game1.svg new file mode 100644 index 0000000..ca4f985 --- /dev/null +++ b/src/static/userInfo/game1.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/static/userInfo/game2.svg b/src/static/userInfo/game2.svg new file mode 100644 index 0000000..ab7fe76 --- /dev/null +++ b/src/static/userInfo/game2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/static/userInfo/game3.svg b/src/static/userInfo/game3.svg new file mode 100644 index 0000000..875bdaa --- /dev/null +++ b/src/static/userInfo/game3.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/static/userInfo/location.svg b/src/static/userInfo/location.svg new file mode 100644 index 0000000..a1c89f9 --- /dev/null +++ b/src/static/userInfo/location.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/static/userInfo/message.svg b/src/static/userInfo/message.svg new file mode 100644 index 0000000..f22ed89 --- /dev/null +++ b/src/static/userInfo/message.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/static/userInfo/plus.svg b/src/static/userInfo/plus.svg new file mode 100644 index 0000000..a4e5876 --- /dev/null +++ b/src/static/userInfo/plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/static/userInfo/tennis.svg b/src/static/userInfo/tennis.svg new file mode 100644 index 0000000..2405eb1 --- /dev/null +++ b/src/static/userInfo/tennis.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/static/userInfo/user1.svg b/src/static/userInfo/user1.svg new file mode 100644 index 0000000..605867e --- /dev/null +++ b/src/static/userInfo/user1.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/static/userInfo/user2.svg b/src/static/userInfo/user2.svg new file mode 100644 index 0000000..86e660b --- /dev/null +++ b/src/static/userInfo/user2.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From b14985d8dd98abd8487ba7e075a7f711878530ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Sun, 24 Aug 2025 23:48:02 +0800 Subject: [PATCH 13/22] feat: upload cover not over yet --- src/app.config.ts | 2 +- src/components/UploadCover/index.scss | 127 ++++++++++++++ src/components/UploadCover/index.tsx | 122 ++++++++++++- .../UploadCover/upload-from-wx.scss | 11 ++ src/components/UploadCover/upload-from-wx.tsx | 47 +++++ .../UploadCover/upload-source-popup.scss | 156 +++++++++++++++++ .../UploadCover/upload-source-popup.tsx | 161 ++++++++++++++++++ src/components/index.ts | 20 ++- src/config/images.js | 9 +- src/pages/detail/index.scss | 14 +- src/pages/detail/index.tsx | 13 +- src/pages/publishBall/publishForm.tsx | 24 +-- src/scss/images.scss | 15 +- src/services/detailApi.ts | 41 +++++ src/services/httpService.ts | 23 +-- src/services/publishService.ts | 68 +++++++- src/services/uploadFiles.ts | 75 ++++++++ .../icon-arrow-left.svg | 0 .../icon-logo-go.svg} | 0 src/static/emptyStatus/comment-empty.png | Bin 0 -> 11448 bytes src/static/emptyStatus/comment-failed.png | Bin 0 -> 17514 bytes src/static/emptyStatus/publish-empty.png | Bin 0 -> 13137 bytes src/static/emptyStatus/publish-failed.png | Bin 0 -> 17756 bytes .../publishBall/icon-circle-select-arrow.svg | 3 + .../publishBall/icon-circle-select-ring.svg | 3 + .../publishBall/icon-circle-unselect.svg | 3 + 26 files changed, 881 insertions(+), 56 deletions(-) create mode 100644 src/components/UploadCover/upload-from-wx.scss create mode 100644 src/components/UploadCover/upload-from-wx.tsx create mode 100644 src/components/UploadCover/upload-source-popup.scss create mode 100644 src/components/UploadCover/upload-source-popup.tsx create mode 100644 src/services/detailApi.ts create mode 100644 src/services/uploadFiles.ts rename src/static/{publishBall => detail}/icon-arrow-left.svg (100%) rename src/static/{publishBall/icon-logo-go!.svg => detail/icon-logo-go.svg} (100%) create mode 100644 src/static/emptyStatus/comment-empty.png create mode 100644 src/static/emptyStatus/comment-failed.png create mode 100644 src/static/emptyStatus/publish-empty.png create mode 100644 src/static/emptyStatus/publish-failed.png create mode 100644 src/static/publishBall/icon-circle-select-arrow.svg create mode 100644 src/static/publishBall/icon-circle-select-ring.svg create mode 100644 src/static/publishBall/icon-circle-unselect.svg diff --git a/src/app.config.ts b/src/app.config.ts index 29bdd35..6ea5cc9 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -3,7 +3,7 @@ export default defineAppConfig({ 'pages/login/index/index', 'pages/login/verification/index', 'pages/login/terms/index', - // 'pages/publishBall/index', + 'pages/publishBall/index', // 'pages/mapDisplay/index', // 'pages/list/index', 'pages/index/index', diff --git a/src/components/UploadCover/index.scss b/src/components/UploadCover/index.scss index e69de29..ff76d06 100644 --- a/src/components/UploadCover/index.scss +++ b/src/components/UploadCover/index.scss @@ -0,0 +1,127 @@ +@use '~@/scss/images.scss' as img; +@use '~@/scss/themeColor.scss' as theme; + +.upload-cover-root { + display: flex; + width: 100%; + height: 112px; + margin-bottom: 8px; + position: relative; + align-items: flex-end; + + &.upload-cover-act-center { + justify-content: center; + } + + .upload-cover-act { + display: flex; + width: 108px; + height: 108px; + padding: 16px 12px 10px 12px; + margin-top: 4px; + box-sizing: border-box; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 20px; + border: 1px dashed rgba(0, 0, 0, 0.12); + background: theme.$page-background-color; + box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); + z-index: 1; + + .upload-cover-act-icon { + width: 20px; + height: 20px; + } + + .upload-cover-text { + color: var(--Labels-Secondary, var(--Labels-Secondary, rgba(60, 60, 67, 0.60))); + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 166.667% */ + } + } + + .cover-image-list-container { + position: absolute; + left: 114px; + top: 0; + width: calc(100% - 114px); + overflow-x: scroll; + height: 112px; + + &.full { + left: 0; + width: 100%; + } + + .cover-image-list { + width: auto; + height: 112px; + display: flex; + gap: 6px; + justify-content: flex-start; + align-items: flex-end; + flex-wrap: nowrap; + flex-shrink: 0; + flex-grow: 0; + + .cover-image-item { + display: flex; + width: 108px; + height: 108px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 20px; + position: relative; + border: 1px solid rgba(0, 0, 0, 0.12); + box-sizing: border-box; + + .cover-image-item-image { + width: 100%; + height: 100%; + aspect-ratio: 1/1; + border-radius: 20px; + } + + .cover-image-item-delete { + position: absolute; + top: -4px; + right: -4px; + width: 16px; + height: 16px; + } + } + } + } +} + +.upload-source-popup-container { + width: 100%; + height: auto; + display: flex; + flex-direction: column; + padding: 26px 0; + box-sizing: border-box; +} + +.upload-source-popup-item { + display: flex; + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + justify-content: center; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + + &:last-child { + border-bottom: none; + } +} \ No newline at end of file diff --git a/src/components/UploadCover/index.tsx b/src/components/UploadCover/index.tsx index 426b037..5698f0a 100644 --- a/src/components/UploadCover/index.tsx +++ b/src/components/UploadCover/index.tsx @@ -1,22 +1,128 @@ -import React, { useState } from 'react' -import { Popup } from "@nutui/nutui-react-taro"; +import React, { useCallback, useState } from 'react' +import { Image, View, Text } from '@tarojs/components' +import img from '../../config/images' +import UploadSourcePopup from './upload-source-popup' +import UploadFromWx from './upload-from-wx' +import { CommonPopup } from '../' import './index.scss' +import { uploadFileResponseData } from '@/services/uploadFiles' -export default function UploadCover(props) { - const { value = [], onChange = () => {} } = props +export type sourceType = 'album' | 'history' | 'preset' + +export type source = sourceType[] + +export type CoverImageValue = { + id: string + url: string + tempFilePath?: string +} + +export interface UploadCoverProps { + value: CoverImageValue[] + onChange: (value: CoverImageValue[] | ((prev: CoverImageValue[]) => CoverImageValue[]) +) => void + source: source + maxCount: number +} + +// const values = [ +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png', +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/cf5a82ba-90af-4138-a1b3-9119adcde9e0.png', +// 'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/49d7cdf0-b03c-4a0f-91c6-e7778080cfcd.png' +// ] + +const mergeCoverImages = (value: CoverImageValue[], images: CoverImageValue[]) => { + console.log(value, images, 11111) + // 根据id来更新url, 如果id不存在,则添加到value中 + const newImages = images + const updatedValue = value.map(item => { + const index = images.findIndex(image => image.id === item.id) + if (index !== -1) { + newImages.splice(index, 1) + return { ...item, url: images[index].url } + } + return item + }) + return [...updatedValue, ...newImages] +} + +export default function UploadCover(props: UploadCoverProps) { + const { + value = [], + onChange = () => void 0, + source = ['album', 'history', 'preset'], + maxCount = 9, + } = props const [visible, setVisible] = useState(false) + const onAdd = useCallback((images: CoverImageValue[]) => { + onChange(prev => mergeCoverImages(prev, images)) + setVisible(false) + }, [value]) + + const onWxAdd = useCallback((images: CoverImageValue[], onFileUploaded: Promise<{ id: string, data: uploadFileResponseData }[]>) => { + onAdd(images) + onFileUploaded.then(res => { + console.log(res, 11111) + onAdd(res.map(item => ({ + id: item.id, + url: item.data.file_path, + }))) + }) + }, [onAdd]) + const onDelete = (image: CoverImageValue) => { + onChange(value.filter(item => item.id !== image.id)) + } + return ( <> - setVisible(false)} round - closeable + position="bottom" + hideFooter > -
-
上传封面
+ + { + source.map((item) => { + return ( + + { + item === 'album' ? ( + + ) : ( + + ) + } + + ) + }) + } + + +
+ {value.length < maxCount && ( +
setVisible(true)}> + +
添加活动封面
+
+ )} +
+
+ { + value.map((item) => { + return ( + + + onDelete(item)} /> + + ) + }) + } +
+
); diff --git a/src/components/UploadCover/upload-from-wx.scss b/src/components/UploadCover/upload-from-wx.scss new file mode 100644 index 0000000..195c5b6 --- /dev/null +++ b/src/components/UploadCover/upload-from-wx.scss @@ -0,0 +1,11 @@ +.upload-from-wx-text { + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 24px; + text-align: center; +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-from-wx.tsx b/src/components/UploadCover/upload-from-wx.tsx new file mode 100644 index 0000000..05c0351 --- /dev/null +++ b/src/components/UploadCover/upload-from-wx.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import Taro from '@tarojs/taro' +import uploadApi from '@/services/uploadFiles' +import './upload-from-wx.scss' +import { CoverImageValue } from '.' +import { uploadFileResponseData } from '@/services/uploadFiles' + +export interface UploadFromWxProps { + onAdd: (images: CoverImageValue[], onFileUploaded: Promise<{ id: string, data: uploadFileResponseData }[]>) => void + maxCount: number +} + +export default function UploadFromWx(props: UploadFromWxProps) { + const { + onAdd = () => void 0, + maxCount = 9, // calc from parent + } = props + const handleImportFromWx = () => { + Taro.chooseImage({ + count: maxCount, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + }).then(async (res) => { + // TODO: compress image + // TODO: cropping image to const size + let count = 0 + const files = res.tempFiles.map(item => ({ + filePath: item.path, + description: 'test', + tags: 'test', + is_public: 1 as unknown as 0 | 1, + id: (Date.now() + count++).toString(), + })) + const onFileUploaded = uploadApi.batchUpload(files) + onAdd(res.tempFiles.map(item => ({ + id: Date.now().toString(), + url: item.path, + })), onFileUploaded) // TODO: add loading state + }) + } + return ( + + 从相册添加 + + ) +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-source-popup.scss b/src/components/UploadCover/upload-source-popup.scss new file mode 100644 index 0000000..999c934 --- /dev/null +++ b/src/components/UploadCover/upload-source-popup.scss @@ -0,0 +1,156 @@ +@use '~@/scss/themeColor.scss' as theme; + +.upload-source-popup-text { + width: 100%; + height: 56px; + padding: 16px 24px; + box-sizing: border-box; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 24px; + text-align: center; +} + +.upload-popup { + width: 100%; + padding: 26px 0; + box-sizing: border-box; + + .upload-popup-title { + display: flex; + padding: 18px 20px 10px 20px; + align-items: center; + align-self: stretch; + font-family: "PingFang SC"; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 150% */ + letter-spacing: -0.23px; + } + + .upload-popup-scroll-view { + max-height: calc(100vh - 260px); + overflow-y: auto; + + .upload-popup-image-list { + width: 100%; + padding: 0 16px; + box-sizing: border-box; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px 10px; + + .upload-popup-image-item { + aspect-ratio: 1/1; + border-radius: 9px; + border: 1px solid rgba(0, 0, 0, 0.12); + box-sizing: border-box; + background: rgba(0, 0, 0, 0.06); + margin: 0; + position: relative; + + &:not(.selected) { + &.disabled { + opacity: 0.5; + pointer-events: none; + cursor: not-allowed; + } + } + + .upload-popup-image-item-image { + width: 100%; + height: 100%; + border-radius: 9px; + margin: 0; + } + + .upload-popup-image-item-select { + position: absolute; + top: 5px; + right: 5px; + width: 14px; + height: 14px; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + + &.selected { + background: rgba(0, 0, 0, 0.5); + + .select-image-icon { + width: 7px; + height: 7px; + } + } + } + + .select-image-icon { + width: 14px; + height: 14px; + } + } + } + + .upload-popup-image-list-empty { + width: 100%; + height: 60vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + } + + .upload-popup-image-list-empty-image { + width: 80%; + aspect-ratio: 4/3; + height: auto; + } + + .upload-popup-image-list-empty-text { + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 150% */ + letter-spacing: -0.23px; + } + } + + .upload-popup-footer { + display: flex; + width: 100%; + height: 62px; + padding: 8px 10px 10px 10px; + box-sizing: border-box; + justify-content: center; + align-items: flex-start; + gap: 8px; + + .upload-popup-footer-cancel, .upload-popup-footer-confirm { + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + box-sizing: border-box; + height: 44px; + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + flex: 1; + } + + .upload-popup-footer-cancel { + background: theme.$page-background-color; + } + + .upload-popup-footer-confirm { + background: theme.$primary-color; + color: rgba(255, 255, 255, 0.5); + + &.active { + color: #fff; + } + } + } +} \ No newline at end of file diff --git a/src/components/UploadCover/upload-source-popup.tsx b/src/components/UploadCover/upload-source-popup.tsx new file mode 100644 index 0000000..ccb156f --- /dev/null +++ b/src/components/UploadCover/upload-source-popup.tsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect } from 'react' +import { Image, View, Text, ScrollView, Button } from '@tarojs/components' +import Taro from '@tarojs/taro' +import img from '../../config/images' +import publishService from '../../services/publishService' +import { CommonPopup } from '../' +import emptyStatus from '../../static/emptyStatus/publish-empty.png' + +import './upload-source-popup.scss' + +type SourceType = 'history' | 'preset' + +type ImageItem = { + id: string + url: string + tempFilePath?: string +} + +interface UploadImageProps { + sourceType: SourceType + onAdd: (images: ImageItem[]) => void + maxCount: number +} + +const sourceMap = new Map([ + ['history', '历史图库'], + ['preset', '预设图库'] +]) + +const checkImageSelected = (images: ImageItem[], image: ImageItem) => { + return images.some(item => item.id === image.id) +} + +export default function UploadImage(props: UploadImageProps) { + const { + sourceType = 'history', + onAdd = () => void 0, + maxCount = 9, + } = props + const [visible, setVisible] = useState(false) + const [images, setImages] = useState([]) + const [selectedImages, setSelectedImages] = useState([]) + + const handleImageClick = (image: ImageItem) => { + if (checkImageSelected(selectedImages, image)) { + setSelectedImages(selectedImages.filter(item => item.id !== image.id)) + } else if (!outOfMax) { + setSelectedImages([...selectedImages, image]) + } else { + Taro.showToast({ + title: `最多选择${maxCount}张图片`, + icon: 'none' + }) + } + } + + useEffect(() => { + if (visible) { + publishService.getPictures({ + pageOption: { + page: 1, + pageSize: 100, + }, + seachOption: { + tag: '', + resource_type: 'image', + dateRange: [], + }, + }).then(res => { + if (res.success) { + setImages(res.data.data.rows.map(item => ({ + id: Date.now().toString(), + url: item.thumbnail_url, + }))) + } else { + // TODO: 显示错误信息 + Taro.showToast({ + title: res.message, + icon: 'none' + }) + } + }) + } else { + setSelectedImages([]) + } + }, [visible]) + + const handleConfirm = () => { + if (selectedImages.length > 0) { + onAdd(selectedImages) + setVisible(false) + } else { + Taro.showToast({ + title: '请选择图片', + icon: 'none' + }) + } + } + + const outOfMax = selectedImages.length >= maxCount + + return ( + <> + setVisible(false)} + round + hideFooter + position='bottom' + > + + {sourceMap.get(sourceType)} + {/* TODO: 分页 加载更多 */} + {/* TODO: 图片加载失败 */} + {/* TODO: 图片加载中 */} + + {images.length > 0 ? ( + + {images.map(item => { + const isSelected = checkImageSelected(selectedImages, item) + return ( + handleImageClick(item)}> + + + {isSelected ? ( + + ) : ( + + )} + + + ) + })} + + ) : ( + + + 暂无内容 + + )} + + {images.length > 0 ? ( + + + + + ) : ( + + + + )} + + + setVisible(true)}>{sourceMap.get(sourceType)}选取 + + ); +}; + diff --git a/src/components/index.ts b/src/components/index.ts index cc5f816..6adf6c5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,19 +9,21 @@ import { SelectStadium, StadiumDetail } from './SelectStadium' import TimeSelector from './TimeSelector' import TitleInput from './TitleInput' import CommonPopup from './CommonPopup' +import UploadCover from './UploadCover' -export { - ActivityTypeSwitch, - TextareaTag, +export { + ActivityTypeSwitch, + TextareaTag, FormSwitch, - ImageUpload, + ImageUpload, FormBasicInfo, - Range, - NumberInterval, - SelectStadium, - TimeSelector, + Range, + NumberInterval, + SelectStadium, + TimeSelector, TitleInput, StadiumDetail, - CommonPopup + CommonPopup, + UploadCover } diff --git a/src/config/images.js b/src/config/images.js index 2c7d7b0..7d8921a 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -8,8 +8,8 @@ export default { ICON_COST: require('@/static/publishBall/icon-cost.svg'), ICON_TIPS: require('@/static/publishBall/icon-tips.svg'), ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'), - ICON_ARROW_LEFT: require('@/static/publishBall/icon-arrow-left.svg'), - ICON_LOGO_GO: require('@/static/publishBall/icon-logo-go.svg'), + ICON_ARROW_LEFT: require('@/static/detail/icon-arrow-left.svg'), + ICON_LOGO_GO: require('@/static/detail/icon-logo-go.svg'), ICON_SEARCH: require('@/static/publishBall/icon-search.svg'), ICON_MAP: require('@/static/publishBall/icon-map.svg'), ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'), @@ -18,5 +18,8 @@ export default { ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'), ICON_ADD: require('@/static/publishBall/icon-add.svg'), ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'), - ICON_DELETE: require('@/static/publishBall/icon-delete.svg') + ICON_DELETE: require('@/static/publishBall/icon-delete.svg'), + ICON_CIRCLE_UNSELECT: require('@/static/publishBall/icon-circle-unselect.svg'), + ICON_CIRCLE_SELECT: require('@/static/publishBall/icon-circle-select-ring.svg'), + ICON_CIRCLE_SELECT_ARROW: require('@/static/publishBall/icon-circle-select-arrow.svg'), } \ No newline at end of file diff --git a/src/pages/detail/index.scss b/src/pages/detail/index.scss index ce17fc4..267fa4a 100644 --- a/src/pages/detail/index.scss +++ b/src/pages/detail/index.scss @@ -1,3 +1,5 @@ +@use '~@/scss/images.scss' as img; + .detail-page { width: 100%; height: 100%; @@ -24,6 +26,10 @@ display: flex; align-items: center; + .detail-navigator-back { + border-right: 1px solid #444; + } + .detail-navigator-back, .detail-navigator-icon { height: 20px; width: 50%; @@ -31,7 +37,13 @@ display: flex; justify-content: center; - & > svg { + & > .detail-navigator-back-icon { + width: 20px; + height: 20px; + color: #fff; + } + + & > .detail-navigator-logo-icon { width: 20px; height: 20px; color: #fff; diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx index 7c94d32..7693a67 100644 --- a/src/pages/detail/index.tsx +++ b/src/pages/detail/index.tsx @@ -9,6 +9,7 @@ import { useUserStats, useUserActions } from '../../store/userStore' +import img from '../../config/images' import { getTextColorOnImage } from '../../utils/processImage' import './index.scss' @@ -66,18 +67,10 @@ function Index() { - - - - + - - - - - - + {/* 我的自定义标题 */} diff --git a/src/pages/publishBall/publishForm.tsx b/src/pages/publishBall/publishForm.tsx index cc7e1cc..eaae5bb 100644 --- a/src/pages/publishBall/publishForm.tsx +++ b/src/pages/publishBall/publishForm.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { View, Text } from '@tarojs/components' import Taro from '@tarojs/taro' -import { ImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, NumberInterval, TitleInput, FormBasicInfo, FormSwitch } from '../../components' +import { ImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, NumberInterval, TitleInput, FormBasicInfo, FormSwitch, UploadCover } from '../../components' import { type Stadium, type CoverImage } from '../../components/index.types' import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema' import { PublishBallFormData } from '../../../types/publishBall'; @@ -21,17 +21,21 @@ const componentMap = { [FieldType.CHECKBOX]: FormSwitch, } -const PublishForm: React.FC<{ - formData: PublishBallFormData, - onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void, +const PublishForm: React.FC<{ + formData: PublishBallFormData, + onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void, optionsConfig: FormFieldConfig[] }> = ({ formData, onChange, optionsConfig }) => { const [coverImages, setCoverImages] = useState([]) const [showStadiumSelector, setShowStadiumSelector] = useState(false) const [selectedStadium, setSelectedStadium] = useState(null) // 处理封面图片变化 - const handleCoverImagesChange = (images: CoverImage[]) => { - setCoverImages(images) + const handleCoverImagesChange = (fn: (images: CoverImage[]) => CoverImage[]) => { + if (fn instanceof Function) { + setCoverImages(fn(coverImages)) + } else { + setCoverImages(fn) + } } // 更新表单数据 @@ -80,7 +84,7 @@ const PublishForm: React.FC<{ // TODO: 实现提交逻辑 console.log('提交数据:', { coverImages, formData }) - + Taro.showToast({ title: '发布成功', icon: 'success' @@ -102,8 +106,8 @@ const PublishForm: React.FC<{ console.log(optionProps, item.label, formData[item.key]); if (item.type === FieldType.UPLOADIMAGE) { /* 活动封面 */ - return @@ -149,7 +153,7 @@ const PublishForm: React.FC<{ value={formData[item.key]} onChange={(value) => updateFormData(item.key as keyof PublishBallFormData, value)} {...optionProps} - placeholder={item.placeholder} + placeholder={item.placeholder} /> diff --git a/src/scss/images.scss b/src/scss/images.scss index 32d40b4..a863e16 100644 --- a/src/scss/images.scss +++ b/src/scss/images.scss @@ -16,7 +16,20 @@ $-images: ( 'icon-personal': '/publishBall/icon-personal.svg', 'icon-changda': '/publishBall/icon-changda.svg', 'icon-cost': '/publishBall/icon-cost.svg', - 'icon-remove': '/publishBall/icon-remove.svg' + 'icon-remove': '/publishBall/icon-remove.svg', + 'icon-arrow-left': '/detail/icon-arrow-left.svg', + 'icon-logo-go': '/detail/icon-logo-go.svg', + 'icon-search': '/publishBall/icon-search.svg', + 'icon-map': '/publishBall/icon-map.svg', + 'icon-stadium': '/publishBall/icon-stadium.svg', + 'icon-arrow-small': '/publishBall/icon-arrow-small.svg', + 'icon-map-search': '/publishBall/icon-map-search.svg', + 'icon-heartcircle': '/publishBall/icon-heartcircle.png', + 'icon-copy': '/publishBall/icon-arrow-right.svg', + 'icon-delete': '/publishBall/icon-delete.svg', + 'icon-circle-unselect': '/publishBall/icon-circle-unselect.svg', + 'icon-circle-select-ring': '/publishBall/icon-circle-select-ring.svg', + 'icon-circle-select-arrow': '/publishBall/icon-circle-select-arrow.svg', ) !default; // 图片获取函数 diff --git a/src/services/detailApi.ts b/src/services/detailApi.ts new file mode 100644 index 0000000..e971bf0 --- /dev/null +++ b/src/services/detailApi.ts @@ -0,0 +1,41 @@ +import httpService from './httpService' +import type { ApiResponse } from './httpService' + +// 用户接口 +export interface GameDetail { + id: number, + title: string, + venue_id: number, + creator_id: number, + game_date: string, + start_time: string, + end_time: string, + max_participants: number, + current_participants: number, + ntrp_level: string, + play_style: string, + description: string, + status: string, + created_at: string, + updated_at: string, +} + +// 响应接口 +export interface Response { + code: string + message: string + data: GameDetail +} + +// 发布球局类 +class GameDetailService { + // 用户登录 + async getDetail(id: number): Promise> { + return httpService.post('/games/detail', { id }, { + showLoading: true, + }) + } +} + +// 导出认证服务实例 +export default new GameDetailService() \ No newline at end of file diff --git a/src/services/httpService.ts b/src/services/httpService.ts index 9b7a5fd..fcee8f3 100644 --- a/src/services/httpService.ts +++ b/src/services/httpService.ts @@ -15,6 +15,7 @@ export interface RequestConfig { needAuth?: boolean // 是否需要token认证 showLoading?: boolean // 是否显示加载提示 loadingText?: string // 加载提示文本 + showToast?: boolean // 是否显示toast } // 响应数据接口 @@ -58,7 +59,7 @@ class HttpService { // 构建完整URL private buildUrl(url: string, params?: Record): string { const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}` - + if (params) { const searchParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { @@ -69,7 +70,7 @@ class HttpService { const queryString = searchParams.toString() return queryString ? `${fullUrl}?${queryString}` : fullUrl } - + return fullUrl } @@ -95,7 +96,7 @@ class HttpService { const logMethod = console[level] || console.log const timestamp = new Date().toLocaleTimeString() - + if (data) { logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`, data) } else { @@ -165,9 +166,9 @@ class HttpService { // 处理业务错误 private handleBusinessError(data: any): void { const message = data.message || '操作失败' - + this.log('error', `业务错误: ${message}`, data) - + Taro.showToast({ title: message, icon: 'none', @@ -187,7 +188,7 @@ class HttpService { } = config const fullUrl = this.buildUrl(url, method === 'GET' ? params : undefined) - + this.log('info', `发起请求: ${method} ${fullUrl}`, { data: method !== 'GET' ? data : undefined, params: method === 'GET' ? params : undefined @@ -223,18 +224,18 @@ class HttpService { return this.handleResponse(response) } catch (error) { this.log('error', '请求失败', error) - + // 在模拟模式下返回模拟数据 if (envConfig.enableMock && isDevelopment()) { this.log('info', '使用模拟数据') return this.getMockResponse(url, method) } - + Taro.showToast({ title: '网络连接失败', icon: 'none' }) - + throw error } finally { // 隐藏加载提示 @@ -247,7 +248,7 @@ class HttpService { // 获取模拟数据 private getMockResponse(url: string, method: string): ApiResponse { this.log('info', `返回模拟数据: ${method} ${url}`) - + return { code: 200, success: true, @@ -323,4 +324,4 @@ class HttpService { } // 导出HTTP服务实例 -export default new HttpService() \ No newline at end of file +export default new HttpService() \ No newline at end of file diff --git a/src/services/publishService.ts b/src/services/publishService.ts index 9e73c87..db87e42 100644 --- a/src/services/publishService.ts +++ b/src/services/publishService.ts @@ -16,6 +16,12 @@ export interface PublishBallData { description: string, } +export interface createGameData extends PublishBallData { + status: string, + created_at: string, + updated_at: string, +} + // 响应接口 export interface Response { code: string @@ -23,16 +29,74 @@ export interface Response { data: any } +// export type SourceType = 'history' | 'preset' + +export interface getPicturesReq { + pageOption: { + page: number, + pageSize: number, + }, + seachOption: { + tag: string, + resource_type: string, + dateRange: string[], + }, +} + +export interface getPicturesRes { + code: number, + message: string, + data: { + rows: [ + { + user_id: string, + resource_type: string, + file_name: string, + original_name: string, + file_path: string, + file_url: string, + file_size: number, + mime_type: string, + width: number, + height: number, + duration: number, + thumbnail_url: string, + is_public: string, + tags: string[], + description: string, + view_count: number, + download_count: number, + status: string, + last_modify_time: string, + } + ], + count: number, + page: number, + pageSize: number, + totalPages: number, + } +} + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} // 发布球局类 class PublishService { // 用户登录 - async createPersonal(data: PublishBallData): Promise> { + async createPersonal(data: PublishBallData): Promise> { return httpService.post('/games/create', data, { showLoading: true, loadingText: '发布中...' }) } + + async getPictures(req: getPicturesReq): Promise> { + return httpService.post('/gallery/sys_img_list', req, { + showLoading: true, + showToast: false, + }) + } } // 导出认证服务实例 -export default new PublishService() \ No newline at end of file +export default new PublishService() \ No newline at end of file diff --git a/src/services/uploadFiles.ts b/src/services/uploadFiles.ts new file mode 100644 index 0000000..ba5a2eb --- /dev/null +++ b/src/services/uploadFiles.ts @@ -0,0 +1,75 @@ +import httpService from './httpService' +import type { ApiResponse } from './httpService' +import Taro from '@tarojs/taro' +import envConfig from '@/config/env' + +// 用户接口 +export interface UploadFilesData { + id: string, + filePath: string, + description?: string, + tags?: string, + is_public?: 0 | 1, +} + +// {"code":0,"message":"请求成功!","data":{"tags":["test"],"create_time":"2025-08-24 22:51:03","last_modify_time":"2025-08-24 22:51:03","duration":"0","thumbnail_url":"","view_count":"0","download_count":"0","id":16,"user_id":1,"resource_type":"image","file_name":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","original_name":"QyoUvEsLG6ci57c7e25cca0845dafed3ee1fde07876d.png","file_path":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","file_url":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","file_size":17756,"mime_type":"image/png","description":"test","is_public":"1","status":"active","width":0,"height":0,"uploadInfo":{"success":true,"name":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","path":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","ossPath":"http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png","fileType":"image/png","fileSize":17756,"originalName":"QyoUvEsLG6ci57c7e25cca0845dafed3ee1fde07876d.png","suffix":"png","storagePath":"front/ball/images/63f56978-ffe9-4397-b897-8aca6f4fdcd8.png"}}} + +export interface uploadFileResponse { + code: number, + message: string, + data: uploadFileResponseData, +} + +export interface uploadFileResponseData { + id: number, + user_id: number, + file_name: string, + original_name: string, + file_path: string, + file_url: string, + file_size: number, + resource_type: string, + mime_type: string, + description: string, + tags: string[], + is_public: string, + view_count: number, + download_count: number, + created_at: string, + updated_at: string, +} + +function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} +// 发布球局类 +class UploadApi { + async upload(req: UploadFilesData): Promise<{ id: string, data: uploadFileResponseData }> { + // return httpService.post('/files/upload', req, { + // showLoading: true, + // }) + const { id, ...rest } = req + return Taro.uploadFile({ + url: `${envConfig.apiBaseURL}/api/gallery/upload`, + filePath: rest.filePath, + name: 'file', + formData: { + description: rest.description, + tags: rest.tags, + is_public: rest.is_public, + } + }).then(res => { + return { + id, + data: JSON.parse(res.data).data, + } + }) + } + + async batchUpload(req: UploadFilesData[]): Promise<{ id: string, data: uploadFileResponseData }[]> { + return Promise.all(req.map(item => this.upload(item))) + } +} + +// 导出认证服务实例 +export default new UploadApi() \ No newline at end of file diff --git a/src/static/publishBall/icon-arrow-left.svg b/src/static/detail/icon-arrow-left.svg similarity index 100% rename from src/static/publishBall/icon-arrow-left.svg rename to src/static/detail/icon-arrow-left.svg diff --git a/src/static/publishBall/icon-logo-go!.svg b/src/static/detail/icon-logo-go.svg similarity index 100% rename from src/static/publishBall/icon-logo-go!.svg rename to src/static/detail/icon-logo-go.svg diff --git a/src/static/emptyStatus/comment-empty.png b/src/static/emptyStatus/comment-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe1e82b55a2c9711be88cc74a7272831256a8a5 GIT binary patch literal 11448 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 ZDf#2V?e34V;>KreYN+X|qLpo5{4eYdy(0hs literal 0 HcmV?d00001 diff --git a/src/static/emptyStatus/comment-failed.png b/src/static/emptyStatus/comment-failed.png new file mode 100644 index 0000000000000000000000000000000000000000..4c6ff523883e909d34b42471525a0af941bb84b8 GIT binary patch literal 17514 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 M6f}_Kau%=tAOE*o1^@s6 literal 0 HcmV?d00001 diff --git a/src/static/emptyStatus/publish-failed.png b/src/static/emptyStatus/publish-failed.png new file mode 100644 index 0000000000000000000000000000000000000000..09b8777c904af9c4871d68e1509046629226a236 GIT binary patch literal 17756 zcmXuL1yq#Z_dPs(5TvA~yE~LFX#|GuR2qhm7D=TWN$KvcAqAwnySoKN8bsd9@B4pQ z!^~R4%=4TZ=bU}^*_SX?Ww{q|w$ffxMpqacHKhUe$7zzeG5TRj&D z1Rd|`7Xgx*P6UBaLFA<+G(FN!+U!*|tun74+fubj^?y2W*Sgb4ynSE3p%7xZ*OBs7 zeD!66JIs3{v2W(#o8~LnRd1vA#`v~#mqmUdgX_2NMF0Mltoqw-6(Q_c24csxtTVDoHW5~2|HD?zLe zkqb_QNm59yH-%G2NYVljOPurch zCL^+)O!8uko_DD5QsljY{Sx>TxWvsP7_+MXsOlUjOa7N*);oRX?)lhc=7W$2EraLH zC+!3@S$SMXlWo^PpZ?efgyZNNP`z~8X^n;U-GAcJabN)b-p=dcMKEPV- zYsz83H&3Pjt@4?eUc3Aih)4!)1)Vg7U!)ZAqqG-ueDSB9Mqxh}D!0=jNV@vAVV`=K z$x*Zlv3%!8$x=J4NC@;r%G;3sWe(ELU@S~|{_YwY$)P7HXS0}F{1_dc%=6v7S>o6G z<@&YH2$NnhJ4Bz+Bj@%sJhfyM)u!)BMTuos8@1_3YBJfQOm9_M(oARxRy9k`3^6mp zKYmN5%5xJG>IpsLIW&<**I>0~L=D8J%^iwwYB5gf8loZV>VCJ2CoknLJ_9_a#bzQhEHFW>%4ZnoX2#N9X0Ih!H_mA*TDVs|bxuKWC$@;CL zdnoPZ)dG2RA_c6h4VYV}Q@lQZm@>w3US!OP|z_uyKIl>C0oPFQzVn`>qW_=2dWz2Ej$+`N3mxmU3$Q1DKo9$DkNrE8k3`?v; zib9{PKJMfq+8H^h(Rk>MN;I@SvgDFc(!)h5HQ!H92if3D<7DN|pJftT;l0BF1Q4ph+q1As(4)=TGyqrHH)EpV#Pt>b# zwp+ME!-lCZfBn`SQ@mMMEWdDf;B^++>U{ax_Svg|UJTx5(Eq@f6U{8tryj> zhnbzA4aO>~H4%PX>ZswGHUDIwb5V^w-XYo@a&0{|9&w)$iyd!}jzivw7+!yl{?}A5 zsiec{&XsReKquZ3V*Fu@>;)I>n?!CuQ(mqzZT8&gp=P+KwIGMs`UhCLfW8~m+j`11 z5tip_$Dd(-sieW$s7xKM{xJU&$SS+A;njnshX zJ9FJ|kQ2oXZxtpT)9<(C$b8`b^Ot+QO9~&zM3E{trPzdUV9Blr2xcrVV=4vne7eNKkg?qP61O3DfWjPJI&=nqaFt z*ZEw^xi(iTAlL@}?`;&B?Q|(!kpYwg9&W*-KP&uagptu!7s@g_-CcZYU1?ddE9SAM z@%%s#7^#0Ynzx|kWk1g$B%G^~CK04m7NC9c-?+E)tHPv0Tc@*!Yowy-lFL=m+3obX zH@`6^TSVoLpGTm!q+@W$6=-wlThIJg)61%}wd3_`Qa>6=J#v6)$IAW>z?0Vomw~%>yte^`d-zY(2R@zSYa~_jv;!R7IukySMgtpAGT-$NP@QkM>D($G1x@E%U^7=%(7KvLh}S zKaPpXWOP}L8@1V(ndg7Jq`*T;&O^LlgF%;rOUp_f!Lr1Q{W`z>y|>o(`_{X>;VX`E z5nhM2Z9r=!=6{Zn=I{FZBo|=JUWUORN%TCX&m3=+Ousx)f4xnJ)#(s|Cd7Us?>u#$JFQT5F|C3I?CE5`b;Ud@&H zzBfxPO`vaxeyu01XWXW#+HA|yaKbf^f*tOxy*Q?Ob+Q`a<8?J~XPh-2a#d&LbUXAi z$-on%aXAXS%@(G|_Mf9Zeq*_z7&VE#^X&YzkiFP4W~g|iTOobiJ&WGQG_#c@^K`;( zFVFB}Cs!8O{>)pQ2!k{6$ zqy}eJOUwD)9CLWs+?+mxygw8VflOKUpyEb5k>RvZ*23j)x-w%!I1L@DVOD=YXAbou z&AzM9W@VX_o7?49$Dbg!q?26I)I_)}8d`KI8wbzR_a!grV7su{eCC&MT~j3GuKn}Q zkNaa&{SZ|{!^o{IQ-S#kgF9W9XJ54Ws^3K2FVgm9d3<3B5%hF^V=$YnKyAV65(+^f z9@fr&7C*S_+?)`uWFNO?*hcps$n%*BLYhvZuaz)~Ai~e6@8kTf6N2G$oJr8Pm3HE6 zDUMDO+r?4FE90{8z4KxAZ7%%41!|NpY&?EBa&_92Wlr?%h`~ zBZEzXBr(u%{go2GYmoVZ1~wA&(nTnv*`^>2ThdOiJwd12#TiYsDQl6>%Bsh<)qS@x zk%0yhU0y3T$wUZ$=IZLYU>5#i7-0dGS|#FLUH^Bh6PxnYnj1e<>MzzjcR@FB$mf4a zNd(4wlBlBQZ?LK<^iJGc&kP6X*R@$;rv#5SS9fw50_0 z&}_?O9}YGi2Z98)82+0*({D(r5vkpq2UqtGBP%Lk9oI7j{J2@Y3 zO+I~V6-uC+H)!sur^^-~KfhhV`vB?dNw)D*R79`3R1DZV-S6u9Z+cNj4wHO{Py+E8 z0@a^i8va!2mMxUzRN=Z6gXT*NZ2C2D-M<~SZM@Ah{}s~*+CBd~Ha5l$5zXbDpPriP z>VEEI4J&!LoA0Q6I8^adFvHZ5e3jA@OKWakg32(B{BoR2^@Z9%1D<39^I;S(ifHc3 z`9UNH93>h1?Y^kx534P+ou(%u_&lN8j^^-G)X4*G9UmXkd-jrm9c^66E7BhY<}w}D zKKpQ_qLSjaKOO((OUnH2G~DxC)V~(_BXg_0|AwFjQhb0A?@QjtnS@+^rpB=yYLqS;eEj<6 zqazHhfyK#5HDwe*wCu#zWX)K^>2!Sl(c_ugoTetCs*|Olr@WN^l&{)&;q zGSx+l5Ere~0A^g(IQh&DFL!dz)#?L{>_Doqub`h0BL0SRYn>>TriPG^5D6;ve&9@s z0j9yz5WaC3!Im5Bm$@tWrT#{7fg=E)w{I{Vb=VuC7iCJ1~Hqojtf!zI4kMWSUdLTs5(|6kgZy zKjvcvir=o#{U`d2l#I*-z9rm#9Z+do%QKvqkqDIy(ElZY`82g)Hdn!Z-tsT>0+y|U zw@TA5WDyH<8PN2{E(nh(BwgPHxF1dxJOpBMe&l~4BS^io07gHn zCb#C)F2$9iZ^-eD!;A`C#GTun!EY^188xIE^7;1mlQa#EG)-*ax)ooV2_I*01v>#c z8X8)X7(bVpKNZj6BDn?4(=?__=dF@Ju|{o~GB)KiqNAhrCEtd`u3_bY69%sG~HBPuQ}>Wglu%0Sd`PXwur$&`DX#aZ*ggH!>EWbI8JnG*V2`( zewfxA(7%7jVJT;D@#-OYciSU!+?8_V1+X>+g3NV~Izk zHXnh(uQYXZz7Y}-z*rpg(Mh$`hWF_@FD`hHLxQ;kcmhvP9WXFon0R^$9bOA(DF0nf zWaZ%?$2L;YdaW9lj9|^i&fZ%$E>^a;OG%y8%SJ&SwtRcC+}Kgs8x*BMl=s?OY(4iS zA5UO=yBN5R#f4`$o}4B*H$JN+_9NxXmoH&P|8Sa;MWd-ucER?k$nyR(xKOH97Ck#N zlYl4bZd;u=H#VkhO>I}t&MO=y`@l0lgI;1d2xa2n2wpzvi)~ylq_Kn1qpW?}&-fD* z6cq1I#YX>SXlO`PTf6UWDh3BFi0jp>u3ji|;MW?hGHs&?)fLUg1>K$**~_@7L4v`= zpU1}rwSDT%aV3sI8IJR_?4nHLfx_<$bowt|xLh2lc`@NHVQs{AM(H=D$G&~Gy}hls zuKI^qa$w>0xQI)2ef>o~vR_sy8Y$;tXx_sJaebC(+N^CvM1-1=QPfdsDLsyvAXYRy z@oGBjpKB`>3gh;3ei*eiSY9G>^3hv623r*B{l~kiM$s{Ur&v|UfrD=5!R#+2bkyQTPN(==|gD^v+X$g!cC7e?oM-=2)~7$w*w&2D|snWJg^ex-%8g~8P|DN~zg^|8GeDVqZS*1T?3 zfB99lu%@809Zjk0Sz#mDPZuhcUQo)?;6z48o0^*X-@*k@@CgauEqwoU&IdQO5Bxf3 zzuaK-zCkQPCd#ft0*Zei^MfKl;#!>L7{k;IKD?_gGa;FS?XcQXu*`5yR=9t=pM{10 z@?{Aw7U8qb-cp?etS}w|&DAm2xs;y1C{i_bb;<2G=FQW>25iZ`q2Xaf6|viVk^q!s zrR(>ezr!0F8>5n;!LCx5C`yeQY`G_It(3~NBKW|LP~>&>pM{;Q)Z1v}E!tV0*jz?* z$-+I-4~dZ~@9y}hNoIFlp?TEOdHqaiOY)G_!)F!w`GL*ZGBh|VEpEfr{@S{_p~1n( zqb)4D*l0lw*Vos}1vR@ECA{2Ro|=L*F$Q+whvgMzwrVY*x^I`M8KbIXO{vH*w}H1z z+fA{(Aj5pV=5xJTFzzEp38p(ZB;@RJ{@wOihH&bs3aVutaT=dJu+YMje2F$f6Nfi+ z^i)^ZdN@~0>Xp)q6jxOY>WBZRl@#uUW5@*6(X*vaQ4cOJFGrA0z`O*jp!7uOi}-d> zzVmzFby)izwz+9?^U!xp<9)SgrKYbx^amG#+ew_hy)d;{z9)2fm|0^W@B#%LowCbL zEXdl`E0ck|ucuoRA9X25D#RU6(mbnXP$)Wy)gPHSmmtQx_wwqBWGK9>xcI%%H$eup zw5^-eBMqUR5hGTJN7cJ3Bx?6$=z7?4m*krycG8G@DY0Ld9$!PcEOl6{K|~Tm6Tez4 z<4cP|Cy@fE96U%!NH(tTxw*JL+s;=WuKAM^6HCzHn%u525pLT)PvppNDJTr|X2ZkD zon!#eN`g5kS@%Vi&?U=vKsG=MvX&MB%~!odVw#aWa{fnfryBBaC9_P zaoMN4fZv(}RTR?QD6RU117#qDjA9Y1A^!LC%CoH&$q>K(6pExIQovLuS_qQGPJVU` z$PzzJ+_zUhuJ@j=nQadUDf}vkK?e?grMza=TAj;aBwi- z=^08b^CF^)z_EF?;=kXXl?L|t*;FSPis{g0_C|Bsq}tk7mQMAtu{cumIfn=UU6TvD zZveYyqnhOB_*qS81I_I#h_z?v4Bg!Jk>X33iz@geij{W#S#WqFdb`;2dW&YJdN&>4 z_eYa2G=88Y!wi{Q?)>{#MC|_OYwclvcUM<)e8nWL@5K_Xaz+jx%dn&Pc8g~%<#?7- z4^Zg=VX*Znb|w~6X3$N$%DCc=mz9!s6BhlQ=Y}KY!2Qm>4?5gj7!Y#eT zp|(VbKNsmlxZ_n&8P7@)qh;~(Q&aw zH5H!g_&eNTt&QAO+oy&d_#&lVbvL1aAf~Lny?@ue5shIw_fj@AH&<*lO!k{8h5}MW zeSN|psiU{I$cJYY&CPm*{=vzff)(WrZ|S<&+GSA&4@TnC8)k4fFFj9yk3Or;NQS26 zh!gR=FK?)H=#Hh88$RjiKcA`E8+#T0Eh#EG2azFMmZMH+tFo$!bN_gfjQ~A0Ep2jQ zBKuN%YHD^is7$A-)OQtx{!YufN#h2q`>T`Mz6BRG#m@`jvlW+OTeseh*gjd_xgO*M z+DkV#P1Lm+m?4OXiHUm6QQ+C*&mf~ojc)SKD5UQ0?k113+){1QGL8NneOtqsxQSn9 zHQlK4KN)aIv(7O(MY5vbXqUE_s?q%_!ibCCu8~8 zdSuv%C#{R-3?QFDwAebz`WHxyMl6#yD(k~@-qRCUS@&dkMMXuhGqK*}%1UH~L1U?N z#alZ6oE&Nb#UiDzPHKw&0^QK_|JGDKtYp(zL#o|R^jVI3{_K8xr~WRU!Lzt!9`D7A z7Xlu?vsvE6zvl{l&-K<#Npj$ys}Zu{jCwX4{qA;yN{kY43`xDFIcuU^Vq%rYc2-hS zQfn>gPgud@xS~-LhYxv>)=S7XDng$H%D^HwWg*3c-{rU7Bw;VQ^t9N0tF0ePD&_G) zy1(r0SynXW7ywUV=_0^D=H}*>a2u6o52$feQ~WZ#OEO)GzWFrN=U9~L2Au2V^}uC+ z3+R*3nV7A13{go>Cd1Z92;n>c^U9ytUith%g$t1MJVO|5sdp5BKAbAzow^V81D9QI z_kjl4`}$_~-qYJ42ob_vi+}q2-p=aGY+2*h8>xt*lYA6@T~uBDwYho{Qwv+tol%hp z7!feMi&nhrA1q1j!Q_h3W6GCS(wgZRRUm(g4?Z6PujU>ITtc98m1r&=6c3eEDRatr zEYxkMeoDh`ZDC=ktG!cTR~moXefj<2YWcgj6as)`*7o)mSFiIkQ0W7Is@%Ah>I|xZ z0xm;3FOO}xu>J25E6fP7#%`r4wzT>%$7U5o&_mbU+S;kF56PY(y9roBqo)W?b4#@S zhb6v}tvF82h6q*tE?*$1zc6^RPuwM4MQzaZxrk<<6D%w&O`#=$JAGLqpKx7VTr}r1 z)6?^rVeZ@}zu&H;MWh<$PAVruq0%QE&ykB;GqCG2c_wL+<|ZaSgSFu>W!ZKVsP?*c zsBdi4FuDc-oQah+kFPTT`;YVY;+MYolfglLJ;#gD3#WCjIr`h|@LqcKM#46|^-7`h(`MH&q%5Yn!oyn!8@UAXN*zx3d z`EVyV{U4(l5*Y;2mq-Td*v1b>Ys7DdFC@wG0CwPY* zBw24XQJ}H;M`nfufh-EA7EuA+2B0GMpG!f*I+f;s^|k5AaJ9=4k8%iMEtzCKQmpL+ zVbazr&@pti2*O&vC2|D7PTzboAoX3FV_1fPD-sbC=QKz4>oz;n!{J2F+ys$B<~|kG z8>t=!7~=7^GMTTI2eRROt1|3N^SaJ`7)hz$;u8@Xno$U_E}8T22q@Ix;6Kcpy#gC#groI?8(!1HtOPF^Sz&r$w6n)U$E+^V5Te&Lt%!ouIl( zeiveym9M@3X>4?KvnvRFhEdw_V0vYxy!ctjo>wGxEwK;*{N{7V7A{7`r_CTj&1THe zIVSe~!dq{#bdgVS26jRqc&MsPeU{M+p)iBp=cv!Av(EwC7Y>&Av}lg8>i)s^?~zW2 zq>|E$Os`WxDJ8g@P{t<>=@sB>0O48iYQ6&HH5uk~3-LE8tN0J}{B_!W*CgQ`_pKe> zF5{v_LvU+lf`_WdJ5J-gLdYR@vbXgZB?ZNpkd3&DP#qNtCLl50xewMeBA#%9CLT}d zw1W+Hm~>M%bI-5q?XBL3Pn)tj+S<7^!y{t5*C|G}YRjAq5OoUL59Uvn9$i{DXX&?Q zHV1;`^z!l=LpNxU!<&jOv!RFW=32P3d^5EQQ)QKaglm%=tc#vgbgXBccO_N88>*|t zT}V@PVNu*>zRo1Pc+axaZ+DVNeEvu*)Yi;dT3T8=)+~7D7Z%FS9y=*2 zJ?^wrTbNZ4FRBZ(w4{9sSUTfLbyPD8(JXa7+mcy2zjKoS$whWurH&#F+f<`W`GkqU zcKi=A+(ZFrYQvmvH{|eZyhcRBgU^m;p6XO|8a=6OV%eIq#A{KyYYfg-q<vu^|Q&anLcjwuyXI-PIt-ZB3nueN{_w(g5>#!wXg<~R; z3U=Pqo$z~`8eLUg-5cEoM+XN?-;Q^UbM`VaNGzYMWl-wdBk)Q&Yy&{QG z3&emF78d?K>!-|XY}7fskKU_^hV)r~cR6n$XCY`a7qy9U)C18n@uB!juc?HOk0|il z=Ez}kkkElC-;5cf?hcQRhK|UFq1Ce9mKF@DNP-poSK@;Vx3KS zq-s|(yXQq55=gP45*ngA@oHZO&~*lCF0HEz4fdqdfyhcuuWP^dN*YXvFRI(x*r>6= zRxa@;72juABG16!N2i+1fWF8O{#fcM?%J@G#45=E#y`?SUqqyF{2Wq{d-W=#jfk>M z7sexFzdMxJv04k|g~#!exbn-pgl* z*BZ890`(0H+!lMt(|C8yLqIR9HIv4(b_URm@LJ5e#c~@%CEJhj-4sIwQf3=TB;*(7 zB?#hF*y1)e%!zP2zBGp1LPqd+6r!+@{}iJbQA{Tk`M(6J$walL-34Dwdh@8HWBnhs>P}CNv~oGa z4VYViRTLfz7wv*aEv2}K@@pH-9Y4Mn{eScU6`KF_q2|ewhK9z>z3GX&FdPnUFk!<@sHkj@h7UN{p4(sWL8T z$()51kVIexNm2zSNPW29^6SQg2x4eA7z7NEv8kYXDxoy+i+p9T0omZ@+ ztgLQe+Z+CJ#*1y$fq%{On4QB~{^<(4!{00}_3r+crg9yW5Pkrn%jw_WPe;d77Y_OU zE)v+a%J@WL$AY4w6_*6bQm-qKYY1YJ;Ct?^HD>*oEVjEeV32yKfIcPk^ztgvwSOv_ zJTF<|=a|Z76?o5y<9`h3&omtq2dr(`;MQ-$>J2CMGrgkG^+`C4zb9YX(wBi^k{Fb- z3Vl^p%$B(Icw}d1Yj)C6jHno0J3Dl|Y6dl?L0f@ex;KA-;;-Z-QIo%`o%28z+M zg@wry-dK~75{#)d@g>EJ^c{(LeqbMh_2jix3NN#GEl}IsoWh92BU!ryynfSF2o!{l z`(xxc-eR*oP=EI<*W5I_!rZj!3|+MAYh3q@c=PWp=H{rZYGQdg?dsVH zw0hC5cFPqrB;eZBau7_fnyzl~C13IT8r|Xs8EW~tc#N z@4sjLijsW%{Boji4YjqCuDk$KQ*8V?3&a3Hvt5w3l3>JgpzQ1Ek)GOrjl=6uK>v9! z1)@-`%l7;J;o#@b9>RCtXA9^24lCK7qG_W^e6qQF-0sb9rKAEjS5*N)K!yGG_Pd=L z;7hi13pGIW`1j{qM|M#WV@aBzhzPWefq9nF(8PrLqbT!xSAJK}6_|N0=6$>S4YflV zg;~)rWliy=I!#rG_<{`M%iRPYDaH||=}p)Q5A z^mHicpICMnma=Kue^=z-H4?cWynkybGsl-u7d&VM}BQK*F4&0CR`yR2u$mY`Uq5DcJn_BFYsE zd(&5eUBSY_;$|ZNtG1!(Y0*JuR((KZ2F(XhUQ>SRu7O<->d^6D{MjAW|3J@fS*NG% zE}}67-v@Nir{&QwOk&mrkTZY+7-ktw06bPBVxvoWDs zZd!>B&lEAs*?1452`RHm%gdz!7OO1hWwyz_ej|`K4XUcEEB7POwtBGdpj#z}KB-d8MT+N5?}oQ~7iVudsRbQpR=9N-InC?-rN{bQDh)lNeS9HwXOnQ5j2fZ+1(G*$3W7X4UCadA~Dysp;7!eRlW)u@~9t;$VgY!k0oap$?2?LM_XU zABOM5;s@#8%{(z@asK}W8BAl9NB5&62k1qyBn|P@;((5j0)SvFFh9@7hwHOzzsGC6 z4l!cjiAb5AN6h7Vb-{N_=_5`;8BhgD;BtKSOVVyiVSv(DOhlTrb}gai@j0s)>{-q; z|9#T9Zh7%Am6ns6D;cYG-spGVsMkRd=_yHH`K}{+aZ!)K*5y@*42Bz!20*v00Y0;d zv7#y0K8`C$GM+)1w3w_|8=%wM0G(q41I68+pMh5O*`l{XE{qpCAksAOie|WlrV#-R$EqK z13l*^-v=QIG4BC$A7#r4mb|$5obe=#I7D$n!RO|BF#oR_r$ySiveQBlMIg4;z^^9d zD7jsv0y?QU&nHlMR>lE$%wVsI>l}~B?M-x#`tc1j4rU0R`%~|h0qi6mWihMW;QxLA z*wj?L3jsMBio}lBs6oSb9_E_6*XEmnu8-XFoNst&Xv4WVS=Z|9ZHg*=wW}0$yGe63 z@wcfdb&>m@bt7{ChY)8mM?huR314Tm3x7W29eO0u9kq_r@9$k>Rc~3nprJ3+00aym>)k8O$5e=r2B_spfsxxTRv6_Gf z^3-F4Ek#kGWcB&67ZmU6@5R&e^74AzpEUAj-L%**^YwOafXVaz-T8dxDj^iibLLB4 z2#7W#NTd7b76~Vjd5r(iOW5IL!``(&VDPOd{wA|FpzBve$es?0QEp=^$Gddi`w4R(Na3;c>9!~T3#B{eSuBn-ztJMwmmwLwd% zJl59M*{?obO%*6Yw{5YH7d*aHdJf$BD6|d0P?n-d`D-cI6i|qTegAs+{hf~wCmHu< z=m%C@dN5!g%WyOSR8LO)P))jeC zBF(PzI%`@low3?tnBa+JP-qTJEjMmURX8E(>FET5hT`(XXh77ydAuE8Aus3wDQ0PT z8BdI{ISpoU!pO*2l5Qq}au`#k691NC*k2V>693)GKshk!@i*4vdjal<0#J#xwY7no zL;d@l9Hq}QfT)$2G`NL@(W~a4`u+gR%9Nn=&Pwu` zMAO%w&EH+#?3!vGvt>S!WDZO{lN05NqNc=@l>6Fg|ACYj+}_?U*WL%yhq63%E>>G) zPEdNK3&?O$ALW|UZ=r-5%he``YaZJ#x+C=1)Qi5vO|I7%wz#4Krd09uZ@un{hM%D^ zdGgOqLAzb6ze2Hqw0^8#NqdMoc0ql3bHu82QcC#a=?LmE8xDBQ7rML%z!Dp{6W_H1 zM)U(tfLlOd01wp$TqH3e;m59X$&rKqm+Y{7j8lex>$@q(dZ1e5U-Li&X?envb%cqH zP1?of_3ZnO1RSkl)A*+Z&MhVueRKqy4M8SCVvYv<3h1U64&^6%5TTL)QHXMZr&vxg zG4J`|`7M{|>wv?|J) zqI99&CZ53*IoLmm<(N5kM7>T5yVxO^FF;D_;M{{tKp<2shOz9b$Qzd@48)i%{*K!h zv!+W}VmA7Hcg^e@St!w0^|O%gY;4_J~7dmB16sI3>lMv%ByT z@rAe(2&+G7Bu+Ca4BMT9mUK_Y#sXyq1xLZ^_vG)DgR%tAB%+|W7{zw3e6TFAt2+ei zY4DsKf8&-qmlko+g77b03cKz2J`60V#1cB^n42Op{nmSr0@YU#BMUYOGs!4Isudd)2Jl4Em^}! zi#SoQ7Bvg$@aljJ@oC=z1*8&ZN4gg81^7{qv`@VGK!@AuEfBfbEI2YgPFw0iXoy-yBh*%=pD)-nW9 zqseyq?$4*NI*|x8%^&{leW+Gyjv9c67DskGkoe9Pt+J@`(Mp_}&73>VxD_iN z<5gOyQKDuV>7Hby-&cU_O%@hs#+kUriNq3N2*<+kMtyO5=6JOvI0}^?Oz)}?t5Q+o1X2 zX$7C-ysDY{%2mjKl8V39hpz9Y^-{V0I&<&!qZxPFsBWDn@6R&r-N+{Sww(R348{94 zf2#Mle|9}UQSdsoTo`O<5H|r#Ty4%fd)jaA?gSsVl*w6HSR{cBN$du}xw$kzqQz#= zU^iGB@o=-*9TKCf1M*2kSn+c#E`I(-*B5!YvVYm4^L;YIJbKj6jXR7L(a<#C?Dg-T zoAiU4%fG>Gn1D>s^=)EP3N9eSKbMF@&Nr*)4Ae$I>{jP1=sHOcp)PrQ;@3B=~;1p|Lt$eoeI_&h>(ExNAs7&4wDqg&w)}y zT4Lv60n<!7OP*>MgI=KTDVn;{}|mp=$P}do9gmXl_st zlE}lww6%jnA!KnjKhGt{Bm+>*8haknd*5t^=r-69zdU9mj+=FV{O6O9n0P2Q zmlR`dks)txRzPh$6FPWNpDLQwzwY(na2v?24_KJ91GRd4nON9(%!vmgINL*OYPe-; z>Y&NV$>Ll@J2iadPa7vrSU)Z*F*q_*`>crDb zl!GB{%P;jLk1(zx1c+~iUMu!3UF`zl$+^uximy*!OfM)*O(YOz88~q-TVu{fifgLS zZ&v5Gpwcfmm&na}%<`$anX((Vqe#}um>Q)RN{nwW8<)Bwc z5s8qW??87%9EBKcPzAw7KJuERRMo9 z0L5RK6?eR+$T+LfzObTF!H5+I>cG1wTD&}~L2%*autCw*uc$89{gSe%H!?D!`mzyt z6W~?hh0I)XsixF$qs0?U4G|yljvVq%94>-~EJ;q>;w1H6nNA(dX>@6p6H>1mh$NLcT1 z<${7db8x_vtXp+r^y z=aUNWO%bTgq4N9UuSOtL(s}Q&k*5W7ClBJ>7y$89{El&5*9LBSTWfK%cWUJA^@{?? z3Np|+LXY0k@CyotQ9c~VfhMA%5Uu!~POADnD4#|~N14Sq#Z$i;=;}7{$m@`ha)_D& z;#%_EyR5Fq5;5vPGR-N$uNZYrLcD(3PaFFB@4xGpA;aN06ieq6aEJhGz4nayB5-y5?lE;xeoGx1Ra&fc z`w*jRRl_!a8*46`o1g#8ugwPU-+kYUibhk9SArf42C+kdY5X7d%Co~{8b2I!g0 zsglxWXTWpjj{f5F`^&NG($e}h|IJB7qjf-tZeH)(xr}o!?UIIZA$$g60H&}qq|^2O z@<`!~L34$B(ex3$c5gv4cfi!|(RpIWRaSDWeE+0zY|r%td&+OC2URON>|WUdWpD^7 zN}8s2|96_E$PE2D;KuYnbWT{?mnbPI?Yf#~gCCiSWA?CAP?uTOiHmQW%D``doG^y} z`e*0(@)FOZRA^9R2D6!igR+KgMO&Luo$YG|RaMoA9-(N0;g3nFROYVr!qTitF|V)n zfX6xhZM&Y>Kil||$okZmZNcMLzQVx3uy$SrCK@$$uyLh8tIC+IJ)~#Q&|VS1p%wMT z;r+jA-U~^Tgd6vqeCbm7h{+RXwOhM9So?$TrDE=$bMt>xbhIYJ7r(Ld8vg(&w+T0D z#T8==TRy{MA3SVH{oU`5Vq~&RmDlX#AlDbn!fvLb0T2ZE zE4QQs)eI0D$rgN~qEP^>KHcVj81eP$__udHL9vr+h4TZ1pi{Zw-kS2x3I;!t4V>Z86UZ3r&%D0%{=bfU zy~QwX|9?|^=baLkw)>=j1PAq%sc(j%t1v2c1~B}q1%AyAcf35rd{r!0uSKYN;U744 z)7@dz?EkJFZbR*m@v-CK%55oP?={64ik$Y2X+u2r)H_Yh{D|w#ZQqS9|8hH-hLn-T z+uL#^wO@9twKDcjq1l%0dVl-z0T`6492zUg6>4sn*du+LB$kkN;G} z4uh%kp+xy4oC(n&x$n!=9PF~{O!!{eP1zMsANI71dew?f2vV`J@iezTJ`^Z_rIm}W zNAq`7?o1M-0i@YNiyP~u+v8!@Kj!)JdUQ`mK{NRUllE)IH>kLh?uDKvH=O~1Jd6!O z!mlz+nYE3Qc5~x3UWTIs+72vra%QIQ&MjDG*XxVy)}rTcyf=fOuL{h%y1LeT(MTYm zTJ}49+{E(pK>eW%@$RYq4+w7o?_Odl>B6;KA}k>#d5!mZ_~aBizfox(IT7$2%kj-A zmdH2x{Ul7x7n>h{b#Q**mh^lEBz(pbbkUS=)>KezXabA%dov>bg6n2$yCer_*u@;G zO?-vUR3~!@WE=%4^gBO4U7s%}Y6NA>AwjL_aT5E@Ny_Vm#*e?uw)U>?HNsTIA0HI-ClL>q@yvqpMu$az0B7jxvvs$?skd{peow%>{>;_-+cJOr zf(o|;$VS2G4XS6r(X*6$;?ACA&Hy&LqWfnL;e<9r|DQkxumbv5womQMkT-u3z>b4n z7cxv*UtF&L-Qypz%4MeBWuvO zKzY+h-TNXuo){?CfJzyVp3gy&1g&*&Oyca# zl2BLDQgdJVz8!sq^}myN8gt<_1pEjB2c>1gz2;|hq4boD8bSC1=>iPaxEt*LZu|V^!K>FJXA*RmBgeU|8d3N>me*v6c zZES?$F>1D*O^MIRGkL9hY-^alqPJ7$O@UGczK`}F6s5$Zt*!pNJDgM!C-RkKdN#A= zNgoXaq>q&zIczsK+cAjikN>VYu_e(^sUKqqyfzLGraf?!EIsZSye@?Ta4s2rP5Zq6Uj`7x2q%Im%KxgOzoL&bw&6nCrE=( z^KZoQ)KY|){MXLOi9H>I#>d+IUn(kQEaH#9+zMvHwODmZeOrV_*OVl%m0MR%5iuI% zkuu3gZ9PXU>yE5Yt7*1hAJ4c-ZP%3RB0L#t;1J#u^_9ZJWHVWyOnKh1jQ!UDF<`|< zWRwv3c_=DO9S(a=8m-2=jCA;sCHtO9+~5NpBTf$+#UyNb+{|83Nmo{S|RVFe)=lqvH)K23_C?y9Db8~a~ zXs!5mzru@6xZB+`vEzO#Q$)$0C5{&z>1C0GE#fq*t!9A$C5cl7H{ zd07gIMQ##9JD)IrNbB_}bF;K|F};KTV7){{C8T15qh#{{2M-}Q;+RDuw%9~%VMv~5 zDFeDt_#eV3vSjM0rGQ<-XLxa_=MRHW*_h^|V>e-qaC*ZPR~6&%lt5Fp?vGu2N(t-{ zvU$IK&R@`=qw{biGK>0CeK-C$qp_Qxy-`PqM48SGi$dfF|HogY#P867D6+huU^n)Tw8rDc@Ku>Qrr`udNJQG6BX`7?)MDx= z)>7@1=bx1&u#G?1*vO}D;`B3qf#I$l&)cf0PAH&!5;WeR2xV*^59#)}`MtT&Ef=#!idRvq zf{;EwH@b6xH|RgCLC8zxp63`5iCY@3F9Nf`t>?EZTSBMh5&j0hXDGy zh_Q!@8pm)*LPrRxC4_7it+`qmFJ%TF5#G8T + + \ No newline at end of file diff --git a/src/static/publishBall/icon-circle-select-ring.svg b/src/static/publishBall/icon-circle-select-ring.svg new file mode 100644 index 0000000..61d19c5 --- /dev/null +++ b/src/static/publishBall/icon-circle-select-ring.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/static/publishBall/icon-circle-unselect.svg b/src/static/publishBall/icon-circle-unselect.svg new file mode 100644 index 0000000..aae86ec --- /dev/null +++ b/src/static/publishBall/icon-circle-unselect.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 01d3acb6d90f51c0faf3da090ec133886d3f11ba Mon Sep 17 00:00:00 2001 From: juguohong Date: Sun, 24 Aug 2025 23:56:03 +0800 Subject: [PATCH 14/22] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Bubble/index.tsx | 2 +- src/components/CourtType/index.module.scss | 3 + src/components/CourtType/index.tsx | 31 ++++ src/components/CustomNavbar/index.module.scss | 47 ++++++ src/components/CustomNavbar/index.tsx | 73 +++++++++ src/components/GamePlayType/index.module.scss | 3 + src/components/GamePlayType/index.tsx | 33 +++- src/components/ListCard/index.scss | 4 + src/components/ListCard/index.tsx | 1 - src/components/SearchBar/index.module.scss | 1 - src/config/images.js | 4 +- src/pages/list/FilterPopup.tsx | 27 +++- src/pages/list/index.config.ts | 3 +- src/pages/list/index.tsx | 141 ++++++++++-------- src/static/list/icon-change.svg | 6 + src/static/logo.svg | 3 + src/store/global.ts | 61 ++++++++ src/store/listStore.ts | 15 +- src/utils/getNavbarHeight.ts | 13 ++ types/list/types.ts | 7 +- 20 files changed, 393 insertions(+), 85 deletions(-) create mode 100644 src/components/CourtType/index.module.scss create mode 100644 src/components/CourtType/index.tsx create mode 100644 src/components/CustomNavbar/index.module.scss create mode 100644 src/components/CustomNavbar/index.tsx create mode 100644 src/static/list/icon-change.svg create mode 100644 src/static/logo.svg create mode 100644 src/store/global.ts create mode 100644 src/utils/getNavbarHeight.ts diff --git a/src/components/Bubble/index.tsx b/src/components/Bubble/index.tsx index 8f07166..30dee30 100644 --- a/src/components/Bubble/index.tsx +++ b/src/components/Bubble/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import styles from "./index.module.scss"; import BubbleItem from "./BubbleItem"; -import {BubbleProps} from '../../../types/list/types' +import {BubbleOption, BubbleProps} from '../../../types/list/types' const Bubble: React.FC = ({ options, diff --git a/src/components/CourtType/index.module.scss b/src/components/CourtType/index.module.scss new file mode 100644 index 0000000..f7a1246 --- /dev/null +++ b/src/components/CourtType/index.module.scss @@ -0,0 +1,3 @@ +.courtTypeWrapper { + margin-bottom: 18px; +} \ No newline at end of file diff --git a/src/components/CourtType/index.tsx b/src/components/CourtType/index.tsx new file mode 100644 index 0000000..7f8b163 --- /dev/null +++ b/src/components/CourtType/index.tsx @@ -0,0 +1,31 @@ +import { View, Image } from "@tarojs/components"; +import TitleComponent from "@/components/Title"; +import img from "@/config/images"; +import Bubble from "../Bubble"; +import { BubbleOption } from "types/list/types"; +import styles from './index.module.scss' + +interface IProps { + name: string; + options: BubbleOption[]; + value: string; + onChange: (name: string, value: string) => void; +} +const GamePlayType = (props: IProps) => { + const { name, onChange , options, value} = props; + return ( + + } /> + + + ); +}; +export default GamePlayType; diff --git a/src/components/CustomNavbar/index.module.scss b/src/components/CustomNavbar/index.module.scss new file mode 100644 index 0000000..b3a7de1 --- /dev/null +++ b/src/components/CustomNavbar/index.module.scss @@ -0,0 +1,47 @@ +.customerNavbar { + // background-color: red; + + .container { + padding-left: 17px; + display: flex; + align-items: center; + gap: 8px; + } + + .line { + width: 1px; + height: 25px; + background-color: #0000000F; + } + + .logo { + width: 60px; + height: 34px; + } + + .change { + width: 12px; + height: 12px; + } + + .cityWrapper { + line-height: 20px; + } + + .city { + font-weight: 600; + font-size: 13px; + line-height: 20px; + } + + .infoWrapper { + line-height: 12px; + } + + .info { + font-weight: 400; + font-size: 10px; + line-height: 12px; + color: #3C3C4399; + } +} \ No newline at end of file diff --git a/src/components/CustomNavbar/index.tsx b/src/components/CustomNavbar/index.tsx new file mode 100644 index 0000000..4d2e368 --- /dev/null +++ b/src/components/CustomNavbar/index.tsx @@ -0,0 +1,73 @@ +import { View, Text, Image } from "@tarojs/components"; +import img from "@/config/images"; +import { getCurrentLocation } from "@/utils/locationUtils"; +import { getNavbarHeight } from "@/utils/getNavbarHeight"; +import styles from "./index.module.scss"; +import { useEffect } from "react"; +import { useGlobalState } from "@/store/global"; +import { useListState } from "@/store/listStore"; + +const ListHeader = () => { + const { statusBarHeight, navbarHeight, totalHeight } = getNavbarHeight(); + const { + updateState, + location, + getLocationText, + getLocationLoading, + getNavbarHeightInfo, + } = useGlobalState(); + const { gamesNum } = useListState(); + + // 获取位置信息 + const getCurrentLocal = () => { + updateState({ + getLocationLoading: true, + }); + getCurrentLocation().then((res) => { + updateState({ + getLocationLoading: false, + location: res || {}, + }); + }); + }; + useEffect(() => { + getNavbarHeightInfo(); + getCurrentLocal(); + }, []); + + const currentAddress = getLocationLoading + ? getLocationText + : location?.address; + + return ( + + + {/* logo */} + + + + + {/* 位置 */} + {currentAddress} + {!getLocationLoading && ( + + )} + + + 附近${gamesNum}场球局 + + + + + ); +}; +export default ListHeader; diff --git a/src/components/GamePlayType/index.module.scss b/src/components/GamePlayType/index.module.scss index e69de29..2eebf1e 100644 --- a/src/components/GamePlayType/index.module.scss +++ b/src/components/GamePlayType/index.module.scss @@ -0,0 +1,3 @@ +.gamePlayWrapper { + margin-bottom: 18px; +} \ No newline at end of file diff --git a/src/components/GamePlayType/index.tsx b/src/components/GamePlayType/index.tsx index d426d8b..7a70de0 100644 --- a/src/components/GamePlayType/index.tsx +++ b/src/components/GamePlayType/index.tsx @@ -1,13 +1,31 @@ -import PopupGameplay from "../../pages/publishBall/components/PopupGameplay"; -import { View, Text, Image } from "@tarojs/components"; +// import PopupGameplay from "../../pages/publishBall/components/PopupGameplay"; +import { View, Image } from "@tarojs/components"; import TitleComponent from "@/components/Title"; import img from "@/config/images"; - -const GamePlayType = () => { +import Bubble from "../Bubble"; +import styles from "./index.module.scss"; +import { BubbleOption } from "types/list/types"; +interface IProps { + name: string; + value: string; + options: BubbleOption[]; + onChange: (name: string, value: string) => void; +} +const GamePlayType = (props: IProps) => { + const { name, onChange, value, options } = props; return ( - + } /> - + {/* { console.log("onClose"); }} @@ -19,9 +37,10 @@ const GamePlayType = () => { { label: "不限", value: "不限" }, { label: "单打", value: "单打" }, { label: "双打", value: "双打" }, + { label: "娱乐", value: "娱乐" }, { label: "拉球", value: "拉球" }, ]} - /> + /> */} ); }; diff --git a/src/components/ListCard/index.scss b/src/components/ListCard/index.scss index 23ed8af..3cf2e16 100644 --- a/src/components/ListCard/index.scss +++ b/src/components/ListCard/index.scss @@ -38,6 +38,10 @@ .location { display: flex; align-items: center; + font-weight: 400; + font-size: 12px; + line-height: 18px; + color: #3C3C4399; } .location-position { diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index 56bbc42..a5f5f28 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -59,7 +59,6 @@ const ListCard: React.FC = ({ ); }; - console.log("===ttt", !title); return ( {/* 左侧内容区域 */} diff --git a/src/components/SearchBar/index.module.scss b/src/components/SearchBar/index.module.scss index cc20edd..fc822f1 100644 --- a/src/components/SearchBar/index.module.scss +++ b/src/components/SearchBar/index.module.scss @@ -1,5 +1,4 @@ .searchBar { - --nutui-searchbar-padding: 10px 15px; --nutui-searchbar-font-size: 16px; --nutui-searchbar-input-height: 44px; --nutui-searchbar-content-border-radius: 44px; diff --git a/src/config/images.js b/src/config/images.js index f647a3d..d730514 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -25,5 +25,7 @@ export default { ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'), ICON_ADD: require('@/static/publishBall/icon-add.svg'), ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'), - ICON_DELETE: require('@/static/publishBall/icon-delete.svg') + ICON_DELETE: require('@/static/publishBall/icon-delete.svg'), + ICON_LOGO: require('@/static/logo.svg'), + ICON_CHANGE: require('@/static/list/icon-change.svg'), } \ No newline at end of file diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index 8beea3a..382d862 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -8,6 +8,9 @@ import { Image } from "@tarojs/components"; import img from "../../config/images"; import { useListStore } from "src/store/listStore"; import { FilterPopupProps } from "../../../types/list/types"; +// 场地 +import CourtType from "@/components/CourtType"; +// 玩法 import GamePlayType from "@/components/GamePlayType"; const FilterPopup = (props: FilterPopupProps) => { @@ -20,10 +23,11 @@ const FilterPopup = (props: FilterPopupProps) => { onClear, visible, onClose, + statusNavbarHeigh, } = props; const store = useListStore() || {}; - const { timeBubbleData, locationOptions } = store; + const { timeBubbleData, locationOptions, gamePlayOptions } = store; const handleFilterChange = (name, value) => { onChange({ [name]: value }); @@ -42,7 +46,8 @@ const FilterPopup = (props: FilterPopupProps) => { round visible={visible} onClose={onClose} - // closeOnOverlayClick={true} + style={{ marginTop: statusNavbarHeigh + "px" }} + overlayStyle={{ marginTop: statusNavbarHeigh + "px" }} >
{/* 时间气泡选项 */} @@ -68,7 +73,7 @@ const FilterPopup = (props: FilterPopupProps) => { /> {/* 场次气泡选项 */} -
+ {/*
} @@ -82,9 +87,21 @@ const FilterPopup = (props: FilterPopupProps) => { columns={3} name="site" /> -
+
*/} + {/* CourtType */} + {/* 玩法 */} - + {/* 按钮 */}
- - - - - - - - - - {/* 实时进度显示 */} - {loading && ( - - 正在发送 API 请求... - - - )} - - {/* 提示信息 */} - - 💡 API 功能说明 - - • "获取用户信息" - 调用用户资料 API - • "提交统计数据" - 发送统计到服务器 - • "提交用户反馈" - 发送用户评价数据 - • 所有请求都会增加 API 计数统计 - • 请求失败时会自动使用模拟数据 - - - - ) -} - -export default Index diff --git a/src/pages/publishBall/index.tsx b/src/pages/publishBall/index.tsx index 71f92e3..ff903a7 100644 --- a/src/pages/publishBall/index.tsx +++ b/src/pages/publishBall/index.tsx @@ -6,40 +6,40 @@ import PublishForm from './publishForm' import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema'; import { PublishBallFormData } from '../../../types/publishBall'; import PublishService from '@/services/publishService'; -import { getNextHourTime, getEndTime } from '@/utils/timeUtils'; +import { getNextHourTime, getEndTime, delay } from '@/utils'; import images from '@/config/images' import styles from './index.module.scss' const defaultFormData: PublishBallFormData = { - title: '', - image_list: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'], + title: '', + image_list: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'], timeRange: { - start_time: getNextHourTime(), + start_time: getNextHourTime(), end_time: getEndTime(getNextHourTime()) }, - activityInfo: { + activityInfo: { play_type: '不限', price: '', - venue_id: null, + venue_id: null, location_name: '', - location: '', - latitude: '', + location: '', + latitude: '', longitude: '', - court_type: '', - court_surface: '', - venue_description_tag: [], - venue_description: '', - venue_image_list: [], + court_type: '', + court_surface: '', + venue_description_tag: [], + venue_description: '', + venue_image_list: [], }, players: [1, 4], skill_level: [1.0, 5.0], descriptionInfo: { - description: '', - description_tag: [], + description: '', + description_tag: [], }, - is_substitute_supported: true, - is_wechat_contact: true, - wechat_contact: '14223332214' + is_substitute_supported: true, + is_wechat_contact: true, + wechat_contact: '14223332214' } const PublishBall: React.FC = () => { @@ -47,7 +47,7 @@ const PublishBall: React.FC = () => { const [formData, setFormData] = useState([ defaultFormData ]) - + // 删除确认弹窗状态 const [deleteConfirm, setDeleteConfirm] = useState<{ visible: boolean; @@ -195,6 +195,13 @@ const PublishBall: React.FC = () => { title: '发布成功', icon: 'success' }) + delay(1000) + // 如果是个人球局,则跳转到详情页,并自动分享 + // 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰 + Taro.navigateTo({ + // @ts-expect-error: id + url: `/pages/detail/index?id=${res.data.id || 1}&from=publish&autoShare=1` + }) } else { Taro.showToast({ title: res.message, @@ -208,12 +215,12 @@ const PublishBall: React.FC = () => { {/* 活动类型切换 */} - - + { formData.map((item, index) => ( @@ -223,19 +230,19 @@ const PublishBall: React.FC = () => { 第{index + 1}场 - showDeleteConfirm(index)} > - + - + {index > 0 && ( - handleCopyPrevious(index)} > 复制上一场 @@ -244,10 +251,10 @@ const PublishBall: React.FC = () => { )} - updateFormData(key, value, index)} - optionsConfig={publishBallFormSchema} + updateFormData(key, value, index)} + optionsConfig={publishBallFormSchema} /> )) @@ -269,14 +276,14 @@ const PublishBall: React.FC = () => { 确认移除该场次? 该操作不可恢复 - - - + )} From 1cb303b86d495313ceb5a33ea38029e266f0bec9 Mon Sep 17 00:00:00 2001 From: juguohong Date: Sat, 30 Aug 2025 13:40:25 +0800 Subject: [PATCH 21/22] =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ListCard/index.tsx | 18 +++++++----------- src/pages/list/index.tsx | 9 ++++++--- src/services/listApi.ts | 10 ++++------ src/store/listStore.ts | 8 +++++--- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index c91b3d3..ffaca58 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -3,7 +3,6 @@ import Taro from '@tarojs/taro' import img from "../../config/images"; import { ListCardProps } from "../../../types/list/types"; import "./index.scss"; -// import SkeletonComponent from "../../components/Skeleton"; const ListCard: React.FC = ({ id, @@ -15,7 +14,7 @@ const ListCard: React.FC = ({ maxCount, skillLevel, matchType, - images, + images=[], shinei, }) => { const renderItemImage = (src: string) => { @@ -30,28 +29,25 @@ const ListCard: React.FC = ({ // 根据图片数量决定展示样式 const renderImages = () => { - if (images.length === 0) return null; + if (images?.length === 0) return null; - if (images.length === 1) { + if (images?.length === 1) { return ( - {/* */} {renderItemImage(images[0])} ); } - if (images.length === 2) { + if (images?.length === 2) { return ( - {/* */} {renderItemImage(images[0])} - {/* */} {renderItemImage(images[1])} @@ -61,9 +57,9 @@ const ListCard: React.FC = ({ // 3张或更多图片 return ( - {renderItemImage(images[0])} - {renderItemImage(images[1])} - {renderItemImage(images[2])} + {renderItemImage(images?.[0])} + {renderItemImage(images?.[1])} + {renderItemImage(images?.[2])} ); }; diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index 220117b..3e2794e 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -13,14 +13,17 @@ import {useGlobalState} from '@/store/global' import { View } from "@tarojs/components"; import CustomerNavBar from "@/components/CustomNavbar"; import GuideBar from "@/components/GuideBar"; +import { useDictionaryActions } from "@/store/dictionaryStore"; const ListPage = () => { // 从 store 获取数据和方法 const store = useListStore() || {}; - const {statusNavbarHeightInfo } = useGlobalState() || {} - // console.log("===store===", store); - // console.log('===statusNavbarHeightInfo', statusNavbarHeightInfo) + const { statusNavbarHeightInfo } = useGlobalState() || {} + const { getDictionaryValue } = useDictionaryActions() || {}; + console.log('===getDictionaryValue', getDictionaryValue('court_type')); + // locationOptions 室内 + // game_play 玩法 const { isShowFilterPopup, error, diff --git a/src/services/listApi.ts b/src/services/listApi.ts index 77ebd65..eec41ca 100644 --- a/src/services/listApi.ts +++ b/src/services/listApi.ts @@ -1,4 +1,5 @@ import { TennisMatch } from "../store/listStore"; +import httpService from "./httpService"; // 模拟网络延迟 const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -91,14 +92,11 @@ export const getTennisMatches = async (params?: { pageSize?: number; location?: string; skillLevel?: string; -}): Promise => { +}) => { try { - // 生成动态数据 - const matches = generateDynamicData(); - - return matches; + return httpService.post('/venues/list', params, { showLoading: false }) } catch (error) { - console.error("API调用失败:", error); + console.error("列表数据获取失败:", error); throw error; } }; diff --git a/src/store/listStore.ts b/src/store/listStore.ts index b3fe98e..75454f6 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -81,11 +81,13 @@ export const useListStore = create()((set, get) => ({ set({ loading: true, error: null }) try { - const matches = await getTennisMatches(params) + const resData = await getTennisMatches(params) || {}; + const { data = {}, code } = resData; + const { count, rows } = data; set({ - matches, + matches: rows || [], loading: false, - lastRefreshTime: new Date().toISOString() + // lastRefreshTime: new Date().toISOString() }) } catch (error) { From d92419f3c5648a67d1b6857d66d6c92a4bece034 Mon Sep 17 00:00:00 2001 From: juguohong Date: Sat, 30 Aug 2025 18:20:50 +0800 Subject: [PATCH 22/22] =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.ts | 15 +- src/components/CustomNavbar/index.module.scss | 5 +- src/components/CustomNavbar/index.tsx | 8 +- src/components/GuideBar/index.scss | 1 + src/components/ListCard/index.scss | 47 +++++- src/components/ListCard/index.tsx | 155 +++++++++--------- src/components/ListCardSkeleton/index.tsx | 1 - .../ListLoadError/index.module.scss | 44 +++++ src/components/ListLoadError/index.tsx | 24 +++ src/components/SearchBar/index.module.scss | 13 +- src/components/SearchBar/index.tsx | 5 +- src/config/images.js | 3 + src/container/listContainer/index.scss | 8 + src/container/listContainer/index.tsx | 52 ++++++ src/pages/list/FilterPopup.tsx | 19 ++- src/pages/list/index.module.scss | 15 +- src/pages/list/index.tsx | 128 +++------------ src/services/listApi.ts | 63 ------- src/static/list/icon-load-error.svg | 29 ++++ src/static/list/icon-paying-game.svg | 34 ++++ src/static/list/icon-reload.svg | 4 + src/store/listStore.ts | 46 +++++- types/list/types.ts | 3 +- 23 files changed, 456 insertions(+), 266 deletions(-) create mode 100644 src/components/ListLoadError/index.module.scss create mode 100644 src/components/ListLoadError/index.tsx create mode 100644 src/container/listContainer/index.scss create mode 100644 src/container/listContainer/index.tsx create mode 100644 src/static/list/icon-load-error.svg create mode 100644 src/static/list/icon-paying-game.svg create mode 100644 src/static/list/icon-reload.svg diff --git a/src/app.ts b/src/app.ts index 6544603..7ce6cd4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,6 +2,9 @@ import { Component, ReactNode } from 'react' import './nutui-theme.scss' import './app.scss' import { useDictionaryStore } from './store/dictionaryStore' +import { useGlobalStore } from './store/global' + +// import { getNavbarHeight } from "@/utils/getNavbarHeight"; interface AppProps { children: ReactNode @@ -11,11 +14,12 @@ class App extends Component { componentDidMount() { // 初始化字典数据 this.initDictionaryData() + this.getNavBarHeight() } - componentDidShow() {} + componentDidShow() { } - componentDidHide() {} + componentDidHide() { } // 初始化字典数据 private async initDictionaryData() { @@ -27,6 +31,13 @@ class App extends Component { } } + // 获取导航高度 + getNavBarHeight = () => { + const { getNavbarHeightInfo } = useGlobalStore.getState() + getNavbarHeightInfo() + + } + render() { // this.props.children 是将要会渲染的页面 return this.props.children diff --git a/src/components/CustomNavbar/index.module.scss b/src/components/CustomNavbar/index.module.scss index b3a7de1..9c921b2 100644 --- a/src/components/CustomNavbar/index.module.scss +++ b/src/components/CustomNavbar/index.module.scss @@ -1,5 +1,8 @@ .customerNavbar { - // background-color: red; + position: sticky; + top: 0; + z-index: 999; + background-color: #ffffff; .container { padding-left: 17px; diff --git a/src/components/CustomNavbar/index.tsx b/src/components/CustomNavbar/index.tsx index 4d2e368..45f96fb 100644 --- a/src/components/CustomNavbar/index.tsx +++ b/src/components/CustomNavbar/index.tsx @@ -1,22 +1,22 @@ import { View, Text, Image } from "@tarojs/components"; import img from "@/config/images"; import { getCurrentLocation } from "@/utils/locationUtils"; -import { getNavbarHeight } from "@/utils/getNavbarHeight"; import styles from "./index.module.scss"; import { useEffect } from "react"; import { useGlobalState } from "@/store/global"; import { useListState } from "@/store/listStore"; const ListHeader = () => { - const { statusBarHeight, navbarHeight, totalHeight } = getNavbarHeight(); const { updateState, location, getLocationText, getLocationLoading, - getNavbarHeightInfo, + statusNavbarHeightInfo, } = useGlobalState(); const { gamesNum } = useListState(); + console.log("===statusNavbarHeightInfo", statusNavbarHeightInfo); + const { statusBarHeight, navbarHeight, totalHeight } = statusNavbarHeightInfo; // 获取位置信息 const getCurrentLocal = () => { @@ -31,7 +31,7 @@ const ListHeader = () => { }); }; useEffect(() => { - getNavbarHeightInfo(); + // getNavbarHeightInfo(); getCurrentLocal(); }, []); diff --git a/src/components/GuideBar/index.scss b/src/components/GuideBar/index.scss index 608a03a..7cd40b1 100644 --- a/src/components/GuideBar/index.scss +++ b/src/components/GuideBar/index.scss @@ -14,6 +14,7 @@ justify-content: space-between; align-items: center; padding: 20px 12px env(safe-area-inset-bottom); + z-index: 999; &-pages { display: flex; diff --git a/src/components/ListCard/index.scss b/src/components/ListCard/index.scss index 3cf2e16..bcb39aa 100644 --- a/src/components/ListCard/index.scss +++ b/src/components/ListCard/index.scss @@ -1,4 +1,10 @@ -.list-item { +.listCard { + background: linear-gradient(90deg, rgba(183, 248, 113, 0.5) 0%, rgba(183, 248, 113, 0.1) 100%); + border-radius: 20px; + border-width: 0.5px; +} + +.listItem { display: flex; padding: 12px 15px; background: #ffffff; @@ -247,4 +253,43 @@ width: 100%; height: 100%; object-fit: cover; +} + +// 底部 +.smoothPlayingGame { + padding: 5px 12px; + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + + .smoothWrapper, + .localAreaWrapper { + line-height: 18px; + display: flex; + align-items: center; + gap: 5px; + } + + .smoothTitle { + font-size: 14px; + } + + .line { + height: 8px; + width: 1px; + background: #00000040; + border-radius: 99px; + } + + .iconListPlayingGame, + .localArea { + width: 14px; + height: 14px; + } + + .localArea { + border: 0.5px solid #FFFFFFA6; + border-radius: 50%; + } } \ No newline at end of file diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index ffaca58..df80400 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -1,5 +1,5 @@ import { View, Text, Image } from "@tarojs/components"; -import Taro from '@tarojs/taro' +import Taro from "@tarojs/taro"; import img from "../../config/images"; import { ListCardProps } from "../../../types/list/types"; import "./index.scss"; @@ -14,7 +14,7 @@ const ListCard: React.FC = ({ maxCount, skillLevel, matchType, - images=[], + images = [], shinei, }) => { const renderItemImage = (src: string) => { @@ -23,9 +23,9 @@ const ListCard: React.FC = ({ const handleViewDetail = () => { Taro.navigateTo({ - url: `/pages/detail/index?id=${id || 1}&from=list&autoShare=0` - }) - } + url: `/pages/detail/index?id=${id || 1}&from=list&autoShare=0`, + }); + }; // 根据图片数量决定展示样式 const renderImages = () => { @@ -34,9 +34,7 @@ const ListCard: React.FC = ({ if (images?.length === 1) { return ( - - {renderItemImage(images[0])} - + {renderItemImage(images[0])} ); } @@ -44,12 +42,8 @@ const ListCard: React.FC = ({ if (images?.length === 2) { return ( - - {renderItemImage(images[0])} - - - {renderItemImage(images[1])} - + {renderItemImage(images[0])} + {renderItemImage(images[1])} ); } @@ -64,72 +58,87 @@ const ListCard: React.FC = ({ ); }; return ( - - {/* 左侧内容区域 */} - - {/* 标题 */} - - {title} - - - - {/* 时间信息 */} - - - {dateTime} - - - {/* 地点,室内外,距离 */} - - - {location} - - {shinei && `・${shinei}`} - {distance && `・${distance}`} - - - - {/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */} - - - - {Array.from({ length: Math.min(registeredCount, 3) }).map( - (_, index) => ( - - - - ) - )} - + + + {/* 左侧内容区域 */} + + {/* 标题 */} + + {title} + - - - - 报名人数 {registeredCount}/ - {maxCount} - + {/* 时间信息 */} + + + {dateTime} + + + {/* 地点,室内外,距离 */} + + + {location} + + {shinei && `・${shinei}`} + {distance && `・${distance}`} + + + + {/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */} + + + + {Array.from({ length: Math.min(registeredCount, 3) }).map( + (_, index) => ( + + + + ) + )} + - - {skillLevel} - - - {matchType} + + + + + 报名人数 {registeredCount}/ + {maxCount} + + + + {skillLevel} + + + {matchType} + + + {/* 右侧图片区域 */} + {renderImages()} + + {/* 畅打球局 */} + + + + 畅打球局 + + + 场馆方: + + + 仁恒河滨花园网球场 + - - {/* 右侧图片区域 */} - {renderImages()} ); }; diff --git a/src/components/ListCardSkeleton/index.tsx b/src/components/ListCardSkeleton/index.tsx index d19bf56..77441a0 100644 --- a/src/components/ListCardSkeleton/index.tsx +++ b/src/components/ListCardSkeleton/index.tsx @@ -13,7 +13,6 @@ const ListCard = () => { {/* 时间信息 */} - diff --git a/src/components/ListLoadError/index.module.scss b/src/components/ListLoadError/index.module.scss new file mode 100644 index 0000000..1629275 --- /dev/null +++ b/src/components/ListLoadError/index.module.scss @@ -0,0 +1,44 @@ +.listLoadError { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 400px; + + .listLoadErrorImg { + width: 154px; + height: 154px; + } + + .listLoadErrorText { + margin-top: 35px; + margin-bottom: 12px; + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 24px; + letter-spacing: 0px; + } + + .listLoadErrorBtn { + display: flex; + align-items: center; + justify-content: center; + width: 76px; + background: #00000008; + border: 0.5px solid #0000001F; + border-radius: 12px; + padding: 12px 0; + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 24px; + letter-spacing: 0px; + + } + + .reloadIcon { + width: 16px; + height: 16px; + } +} \ No newline at end of file diff --git a/src/components/ListLoadError/index.tsx b/src/components/ListLoadError/index.tsx new file mode 100644 index 0000000..420393d --- /dev/null +++ b/src/components/ListLoadError/index.tsx @@ -0,0 +1,24 @@ +import { Image, View, Text, Button } from "@tarojs/components"; +import styles from "./index.module.scss"; +import img from "@/config/images"; + +const ListLoadError = ({ reload }: { reload: () => void }) => { + const handleReload = () => { + reload && typeof reload === "function" && reload(); + }; + + return ( + + + 加载失败 + + + ); +}; +export default ListLoadError; diff --git a/src/components/SearchBar/index.module.scss b/src/components/SearchBar/index.module.scss index fc822f1..cf5e343 100644 --- a/src/components/SearchBar/index.module.scss +++ b/src/components/SearchBar/index.module.scss @@ -5,9 +5,16 @@ --nutui-searchbar-input-text-color: #000000; --nutui-searchbar-input-padding: 0 0 0 10px; --nutui-searchbar-padding: 10px 0 0 0; + :global(.nut-searchbar-content) { box-shadow: 0 4px 48px #00000014; } + + .searchBarLeft { + display: flex; + align-items: center; + } + .searchBarRight { position: relative; width: 44px; @@ -18,14 +25,17 @@ display: flex; align-items: center; justify-content: center; + &.active { background-color: #000000; } } + .filterIcon { width: 20px; height: 20px; } + .filterCount { background-color: #000000; position: absolute; @@ -41,8 +51,9 @@ justify-content: center; font-size: 11px; } + .searchIcon { width: 20px; height: 20px; } -} +} \ No newline at end of file diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index e4f1fab..8181030 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -12,6 +12,7 @@ interface IProps { const SearchBarComponent = (props: IProps) => { const { handleFilterIcon, isSelect, filterCount, onChange } = props; + const handleChange = (value: string) => { onChange && onChange(value); }; @@ -19,9 +20,9 @@ const SearchBarComponent = (props: IProps) => { <> + -
+ } right={ { + const { loading, data = [], error, reload } = props; + // const { statusNavbarHeightInfo } = useGlobalState() || {}; + // const { totalHeight } = statusNavbarHeightInfo; + + const renderList = () => { + if (loading && data.length > 0) { + return ( + <> + {new Array(10).fill(0).map(() => { + return ; + })} + + ); + } + + if (error) { + return ; + } + + if (loading && data.length === 0) { + return null; + } + + return ( + <> + {data.map((match, index) => ( + + ))} + + ); + }; + + return ( + + {renderList()} + + ); +}; + +export default ListContainer; diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index 382d862..05af903 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -12,6 +12,8 @@ import { FilterPopupProps } from "../../../types/list/types"; import CourtType from "@/components/CourtType"; // 玩法 import GamePlayType from "@/components/GamePlayType"; +import { useDictionaryActions } from "@/store/dictionaryStore"; +import { useMemo } from "react"; const FilterPopup = (props: FilterPopupProps) => { const { @@ -27,7 +29,22 @@ const FilterPopup = (props: FilterPopupProps) => { } = props; const store = useListStore() || {}; - const { timeBubbleData, locationOptions, gamePlayOptions } = store; + const { getDictionaryValue } = useDictionaryActions() || {}; + const { timeBubbleData } = store; + + const handleOptions = (dictionaryValue: []) => { + return dictionaryValue?.map((item) => ({ label: item, value: item })) || []; + }; + + const courtType = getDictionaryValue("court_type") || []; + const locationOptions = useMemo(() => { + return courtType ? handleOptions(courtType) : []; + }, [courtType]); + + const gamePlay = getDictionaryValue("game_play") || []; + const gamePlayOptions = useMemo(() => { + return gamePlay ? handleOptions(gamePlay) : []; + }, [gamePlay]); const handleFilterChange = (name, value) => { onChange({ [name]: value }); diff --git a/src/pages/list/index.module.scss b/src/pages/list/index.module.scss index e1db7c9..7a5f4ac 100644 --- a/src/pages/list/index.module.scss +++ b/src/pages/list/index.module.scss @@ -3,20 +3,23 @@ .listTopSearchWrapper { padding: 0 15px; + position: sticky; + background: #fefefe; + z-index: 999; + } + + .isScroll { + border-bottom: 0.5px solid #0000000F; } .listTopFilterWrapper { display: flex; align-items: center; - margin-top: 5px; - margin-bottom: 10px; + padding-top: 10px; + padding-bottom: 10px; gap: 5px; } - .listContentWrapper { - padding: 0 5px; - } - .menuFilter { padding: 0; } diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index 3e2794e..8e7edb6 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -1,29 +1,22 @@ -import ListCard from "../../components/ListCard"; -import ListCardSkeleton from "../../components/ListCardSkeleton"; -import List from "../../components/List"; import Menu from "../../components/Menu"; import CityFilter from "../../components/CityFilter"; import SearchBar from "../../components/SearchBar"; import FilterPopup from "./FilterPopup"; import styles from "./index.module.scss"; import { useEffect } from "react"; -import Taro, { useReachBottom } from "@tarojs/taro"; +import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro"; import { useListStore } from "@/store/listStore"; -import {useGlobalState} from '@/store/global' +import { useGlobalState } from "@/store/global"; import { View } from "@tarojs/components"; import CustomerNavBar from "@/components/CustomNavbar"; import GuideBar from "@/components/GuideBar"; -import { useDictionaryActions } from "@/store/dictionaryStore"; +import ListContainer from "@/container/listContainer"; const ListPage = () => { // 从 store 获取数据和方法 const store = useListStore() || {}; - const { statusNavbarHeightInfo } = useGlobalState() || {} - const { getDictionaryValue } = useDictionaryActions() || {}; - console.log('===getDictionaryValue', getDictionaryValue('court_type')); - // locationOptions 室内 - // game_play 玩法 + const { statusNavbarHeightInfo } = useGlobalState() || {}; const { isShowFilterPopup, error, @@ -31,7 +24,6 @@ const ListPage = () => { loading, fetchMatches, refreshMatches, - clearError, updateState, filterCount, updateFilterOptions, // 更新筛选条件 @@ -40,8 +32,15 @@ const ListPage = () => { distanceData, quickFilterData, distanceQuickFilter, + isScrollTop, } = store; + usePageScroll((res) => { + if (res?.scrollTop > 0 && !isScrollTop) { + updateState({ isScrollTop: true }); + } + }); + useReachBottom(() => { console.log("触底了"); // 调用 store 的加载更多方法 @@ -82,78 +81,6 @@ const ListPage = () => { }); }); - // 错误处理 - useEffect(() => { - if (error) { - Taro.showToast({ - title: error, - icon: "error", - duration: 2000, - }); - // 3秒后自动清除错误 - setTimeout(() => { - clearError(); - }, 3000); - } - }, [error, clearError]); - - // 加载状态显示 - if (loading && matches.length === 0) { - return ( -
-
加载中...
-
- 正在获取网球比赛数据 -
-
- ); - } - - // 错误状态显示 - if (error && matches.length === 0) { - return ( -
-
加载失败
-
- {error} -
- -
- ); - } - const toggleShowPopup = () => { updateState({ isShowFilterPopup: !isShowFilterPopup }); }; @@ -183,7 +110,14 @@ const ListPage = () => { - + 0} @@ -227,26 +161,16 @@ const ListPage = () => {
- {/* 列表内容 */} - - {!loading && - matches.length > 0 && - matches.map((match, index) => ( - - ))} - - - {/* 空状态 */} - {loading && - matches.length === 0 && - new Array(10).fill(0).map(() => { - return ; - })} - +
- + ); }; diff --git a/src/services/listApi.ts b/src/services/listApi.ts index eec41ca..de2be2e 100644 --- a/src/services/listApi.ts +++ b/src/services/listApi.ts @@ -63,25 +63,6 @@ const mockTennisMatches: TennisMatch[] = [ }, ]; -// 模拟数据变化 -const generateDynamicData = (): TennisMatch[] => { - Promise.resolve((res) => { - setTimeout(res, 3000); - }); - return mockTennisMatches.map((match) => ({ - ...match, - // 随机更新注册人数 - registeredCount: Math.min( - match.maxCount, - Math.max(0, match.registeredCount + Math.floor(Math.random() * 3) - 1) - ), - // 随机更新距离 - distance: `${(Math.random() * 5 + 1).toFixed(1)}km`, - // 随机更新时间 - dateTime: Math.random() > 0.5 ? match.dateTime : "今天下午3点 2小时", - })); -}; - /** * 获取网球比赛列表 * @param params 查询参数 @@ -116,47 +97,3 @@ export const refreshTennisMatches = async (): Promise => { } }; -/** - * 获取比赛详情 - * @param id 比赛ID - * @returns Promise - */ -export const getTennisMatchDetail = async ( - id: string -): Promise => { - try { - console.log("API调用: getTennisMatchDetail", id); - - // 模拟网络延迟 - await delay(600 + Math.random() * 400); - - // 模拟网络错误 - if (simulateNetworkError()) { - throw new Error("获取详情失败,请稍后重试"); - } - - const match = mockTennisMatches.find((m) => m.id === id); - - if (!match) { - throw new Error("比赛不存在"); - } - - console.log("API获取详情成功:", match.title); - return match; - } catch (error) { - console.error("API获取详情失败:", error); - throw error; - } -}; - -/** - * 模拟API统计信息 - */ -export const getApiStats = () => { - return { - totalCalls: 0, - successRate: 0.95, - averageResponseTime: 800, - lastCallTime: new Date().toISOString(), - }; -}; diff --git a/src/static/list/icon-load-error.svg b/src/static/list/icon-load-error.svg new file mode 100644 index 0000000..3b7ad03 --- /dev/null +++ b/src/static/list/icon-load-error.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/list/icon-paying-game.svg b/src/static/list/icon-paying-game.svg new file mode 100644 index 0000000..a312921 --- /dev/null +++ b/src/static/list/icon-paying-game.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/list/icon-reload.svg b/src/static/list/icon-reload.svg new file mode 100644 index 0000000..121dcc5 --- /dev/null +++ b/src/static/list/icon-reload.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/store/listStore.ts b/src/store/listStore.ts index 75454f6..2b767bd 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand' import { getTennisMatches } from '../services/listApi' -import {ListActions, IFilterOptions, ListState } from '../../types/list/types' +import { ListActions, IFilterOptions, ListState } from '../../types/list/types' // 完整的 Store 类型 type TennisStore = ListState & ListActions @@ -40,7 +40,7 @@ export const useListStore = create()((set, get) => ({ { id: 3, label: "10km", value: "10km" }, ], // 快捷筛选数据 - quickFilterData:[ + quickFilterData: [ { text: "默认排序", value: "0" }, { text: "好评排序", value: "1" }, { text: "销量排序", value: "2" }, @@ -75,6 +75,8 @@ export const useListStore = create()((set, get) => ({ ], // 球局数量 gamesNum: 124, + // 页面滚动距离顶部距离 是否大于0 + isScrollTop: false, // 获取比赛数据 fetchMatches: async (params) => { @@ -83,15 +85,42 @@ export const useListStore = create()((set, get) => ({ try { const resData = await getTennisMatches(params) || {}; const { data = {}, code } = resData; + if (code !== 0) { + set({ + error: '1', + matches: [], + loading: false, + }) + } const { count, rows } = data; + const list = (rows || []).map(() => { + return { + id: "3", + title: "黄浦区双打约球", + dateTime: "7月20日(周日)下午6点 2小时", + location: "仁恒河滨花园网球场", + distance: "3.5km", + registeredCount: 3, + maxCount: 4, + skillLevel: "2.0 至 2.5", + matchType: "双打", + images: [ + "https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center", + ], + } + }) set({ - matches: rows || [], + matches: list || rows || [], loading: false, - // lastRefreshTime: new Date().toISOString() + gamesNum: count, }) } catch (error) { - + set({ + error, + matches: [], + loading: false, + }) } }, @@ -100,13 +129,14 @@ export const useListStore = create()((set, get) => ({ set({ loading: true, error: null }) try { - const matches = await getTennisMatches() + const resData = await getTennisMatches() || {}; + const { data = {}, code } = resData; + const { count, rows } = data; set({ - matches, + matches: rows, loading: false, lastRefreshTime: new Date().toISOString() }) - console.log('Store: 成功刷新网球比赛数据:', matches.length, '条') } catch (error) { } }, diff --git a/types/list/types.ts b/types/list/types.ts index 818dd14..e9aa406 100644 --- a/types/list/types.ts +++ b/types/list/types.ts @@ -1,6 +1,6 @@ // 网球比赛数据接口 export interface TennisMatch { - id: string + id: number title: string dateTime: string location: string @@ -39,6 +39,7 @@ export interface ListState { locationOptions: BubbleOption[] gamePlayOptions: BubbleOption[] gamesNum: number + isScrollTop: boolean } export interface ListState {