diff --git a/.cursorrules b/.cursorrules index 751ce41..4ffe28b 100644 --- a/.cursorrules +++ b/.cursorrules @@ -65,8 +65,9 @@ import { HomeOutlined } from '@taroify/icons' - 使用 SCSS - designWidth 为 750 -- 使用 px,会自动转换为 rpx +- 使用 px单位,会自动转换为 rpx - 使用 BEM 命名规范 +- 非特殊说明,不要使用PX ## 常用命令 @@ -83,7 +84,14 @@ npm run build:h5 # 编译 H5 ## 注意事项 1. MobX 组件必须用 `observer` 包裹 -2. Taroify 样式已全局引入 +2. Taroify 样式已引入, +3: 组件优先查看 taroify ui 库是否支持,再根据设计ui样式进行微调可使用 mcp 检查taroify 组件和api: + ``` + - mcp_taroifymcp_list-components:查看所有可用组件 + - mcp_taroifymcp_get-component-docs:获取组件详细文档 + - mcp_taroifymcp_get-component-props:获取组件 Props 和 API + - mcp_taroifymcp_list-component-example:查看组件示例 + ``` 3. 修改 TabBar 需同时更新 `custom-tab-bar/` 和 `app.config.ts` 4. 状态持久化使用 `Taro.setStorageSync` diff --git a/.gitignore b/.gitignore index c295b89..1b541e2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ Thumbs.db # 环境变量 .env .env.local + +docs/ diff --git a/src/assets/icon/phototake.png b/src/assets/icon/phototake.png new file mode 100644 index 0000000..b21effc Binary files /dev/null and b/src/assets/icon/phototake.png differ diff --git a/src/custom-tab-bar/index.scss b/src/custom-tab-bar/index.scss index 3e72670..576506e 100644 --- a/src/custom-tab-bar/index.scss +++ b/src/custom-tab-bar/index.scss @@ -1,9 +1,9 @@ /** * 自定义 TabBar 样式 - 中间凸起 - * 参考:https://developer.aliyun.com/article/1228635 - * 已适配安全区域(iPhone X 等刘海屏) - * - * 注意:Taro 中 px 会被转换为 rpx,使用 Px (大写P) 可避免转换 + * TabBar高度: 129px + * scan图标: 130px × 130px (凸出30px) + * 其他图标: 40px × 37px + * 文本字号: 20px */ .custom-tab-bar { position: fixed; @@ -11,35 +11,35 @@ left: 0; right: 0; width: 100%; - height: 100Px; /* 继续减小高度,给凸起留足空间 */ + height: 129px; /* TabBar总高度 */ z-index: 99; pointer-events: none; - // 背景白色层 #0E0D0D + // 背景层 .tab-bar-bg { position: absolute; - top: 50px; + top: 40px; bottom: 0; left: 0; right: 0; - // height: 10Px; /* TabBar 实际高度 */ + height: 129px; background: #0E0D0D; - box-shadow: 0 -2Px 6Px rgba(0, 0, 0, 0.05); + box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.05); z-index: 1; pointer-events: auto; } - // 中间凸起的圆形白色背景 + // 中间凸起的圆形背景(如需要可以启用) .tab-bar-center-bg { position: absolute; - bottom: 25Px; /* 凸起高度 - 确保顶部不被截取 */ + // bottom: 29px; /* scan按钮底部位置 */ left: 50%; transform: translateX(-50%); - width: 120Px; - height: 120Px; - background: #ffffff; + width: 130px; + height: 130px; + background: blue; + // background: #0E0D0D; border-radius: 50%; - // box-shadow: 0 -2Px 8Px rgba(0, 0, 0, 0.08); z-index: 2; pointer-events: auto; } @@ -50,13 +50,12 @@ bottom: 0; left: 0; right: 0; - // top: -60px; display: flex; align-items: flex-end; justify-content: space-around; - height: 100%; - padding-bottom: 5Px; - padding-bottom: calc(5Px + env(safe-area-inset-bottom)); + height: 129px; + padding-bottom: 10px; + padding-bottom: calc(10px + env(safe-area-inset-bottom)); z-index: 3; pointer-events: auto; overflow: visible; /* 重要:允许子元素溢出显示 */ @@ -69,9 +68,8 @@ flex-direction: column; align-items: center; justify-content: flex-end; - height: 70Px; /* 普通项高度 */ + height: 100%; position: relative; - top: 30px; pointer-events: auto; // 普通 Tab 项内容 @@ -80,19 +78,18 @@ flex-direction: column; align-items: center; justify-content: center; - padding-bottom: 4Px; + padding-bottom: 0; .tab-icon { - width: 41px; - height: 41px; - - margin-bottom: 2Px; + width: 40px; + height: 37px; + margin-bottom: 5px; } .tab-text { font-size: 20px; color: #999999; - // line-height: 28Px; + line-height: 1.2; } } @@ -111,25 +108,23 @@ .scan-wrapper { position: absolute; - bottom: 10Px; /* 调整位置让按钮向上凸起,同时留出底部空间 */ + bottom: -10px; /* 让130px高的scan按钮凸出30px: 129px - (130px - 30px) = 29px */ left: 50%; transform: translateX(-50%); z-index: 10; - .scan-button { - width: 129px; - height: 129px; + width: 130px; + height: 130px; border-radius: 50%; - // background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background-color: #0E0D0D; + display: flex; align-items: center; justify-content: center; - box-shadow: 0 4Px 12Px rgba(102, 126, 234, 0.4); - // border: 5Px solid #ffffff; .scan-icon { - width: 129px; - height: 129px; + width: 110px; + height: 110px; } } } @@ -140,61 +135,14 @@ // iPhone X 及以上设备特殊适配 @supports (bottom: env(safe-area-inset-bottom)) { .custom-tab-bar { - .tab-bar-bg { - // 使用 constant 作为 env 的降级方案(iOS 11.0-11.2) - height: calc(60Px + constant(safe-area-inset-bottom)); - height: calc(60Px + env(safe-area-inset-bottom)); - } - } -} - -// 适配不同屏幕尺寸 -@media screen and (max-width: 320Px) { - .custom-tab-bar { - height: 90Px; + height: calc(129px + env(safe-area-inset-bottom)); .tab-bar-bg { - height: 55Px; - } - - .tab-bar-center-bg { - width: 65Px; - height: 65Px; - bottom: 22Px; - } - - .tab-item { - height: 55Px; - - &.center .scan-wrapper { - bottom: 10Px; - - .scan-button { - width: 58Px; - height: 58Px; - border-width: 4Px; - - .scan-icon { - width: 34Px; - height: 34Px; - } - } - } - } - - .tab-item .tab-content { - .tab-icon { - width: 28Px; - height: 28Px; - } - - .tab-text { - font-size: 20px; - } + height: calc(129px + env(safe-area-inset-bottom)); } - .tab-item.active .tab-content .tab-text { - color: #ffffff; + .tab-bar-content { + height: calc(129px + env(safe-area-inset-bottom)); } } } diff --git a/src/custom-tab-bar/index.tsx b/src/custom-tab-bar/index.tsx index c195aa8..b3cea50 100644 --- a/src/custom-tab-bar/index.tsx +++ b/src/custom-tab-bar/index.tsx @@ -6,6 +6,7 @@ import { CoverView, CoverImage } from '@tarojs/components' import Taro from '@tarojs/taro' import { observer } from 'mobx-react' import { TAB_BAR_CONFIG } from '../constants/config' +import { appStore } from '../store' import './index.scss' // 导入图标资源 @@ -59,6 +60,11 @@ const CustomTabBar = observer(() => { return isActive ? icons.active : icons.normal } + // 如果 TabBar 被隐藏,则不渲染 + if (!appStore.showTabBar) { + return null + } + return ( {/* 背景白色层 */} diff --git a/src/pages/index/index.scss b/src/pages/index/index.scss index 9b6b36c..2241b19 100644 --- a/src/pages/index/index.scss +++ b/src/pages/index/index.scss @@ -4,10 +4,8 @@ */ .index-page { - // 高度为 100vh 减去 tabbar 高度(100Px + 安全区域) - height: calc(100vh - 40Px); - height: calc(100vh - 40Px - constant(safe-area-inset-bottom)); // iOS 11.0-11.2 - height: calc(100vh - 40Px - env(safe-area-inset-bottom)); // iOS 11.2+ + // 高度为 100vh 减去 tabbar 高度 (129px) + height: calc(100vh - 129px + 40px - env(safe-area-inset-bottom)); background-color: #121212; position: relative; overflow-x: hidden; diff --git a/src/pages/message/index.scss b/src/pages/message/index.scss index 7a0fb60..69afa02 100644 --- a/src/pages/message/index.scss +++ b/src/pages/message/index.scss @@ -45,13 +45,9 @@ // Tab 内容区域 - 关键:给 ScrollView 明确的高度 .tab-content { width: 100%; - // 高度 = 100vh - tabbar(40Px) - Tabs标签栏(约88px) - 安全区域 - height: calc(100vh - 40Px - 88px); - height: calc(100vh - 40Px - 88px - constant(safe-area-inset-bottom)); - height: calc(100vh - 40Px - 88px - env(safe-area-inset-bottom)); + // 高度 = 100vh - tabbar(129px) - Tabs标签栏(约88px) - 安全区域 + height: calc(100vh - 129px - 88px - env(safe-area-inset-bottom)); background: #F2F3F5; - - } // 空状态 diff --git a/src/pages/store/index.scss b/src/pages/store/index.scss index 915e1a5..b950282 100644 --- a/src/pages/store/index.scss +++ b/src/pages/store/index.scss @@ -4,10 +4,8 @@ */ .store-page { - // 高度为 100vh 减去 tabbar 高度 - height: calc(100vh - 40Px); - height: calc(100vh - 40Px - constant(safe-area-inset-bottom)); - height: calc(100vh - 40Px - env(safe-area-inset-bottom)); + // 高度为 100vh 减去 tabbar 高度 (129px) + height: calc(100vh - 129px + 40px - env(safe-area-inset-bottom)); background-color: #121212; position: relative; overflow-x: hidden; @@ -245,6 +243,7 @@ } /* 城市选择弹窗 */ +/* 城市选择弹窗样式 */ .city-picker { background: #fff; border-radius: 20px 20px 0 0; diff --git a/src/pages/store/index.tsx b/src/pages/store/index.tsx index 39706fc..1a23bd2 100644 --- a/src/pages/store/index.tsx +++ b/src/pages/store/index.tsx @@ -4,10 +4,11 @@ import { View, Text, Image, Input, ScrollView } from '@tarojs/components' import Taro, { useLoad } from '@tarojs/taro' import { observer } from 'mobx-react' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Popup, Toast, Tabs } from '@taroify/core' import { useCascader } from '@taroify/hooks' import { Arrow, Cross, Search } from '@taroify/icons' +import { appStore } from '../../store' import storeIcon from '../../assets/icon/storeIcon.png' import locationIcon from '../../assets/icon/locationIcon.png' import './index.scss' @@ -120,6 +121,20 @@ const StoreIndex = observer(() => { console.log('门店页面加载') }) + // 监听弹窗状态,控制TabBar显示/隐藏 + useEffect(() => { + if (cityPopupOpen) { + appStore.hideTabBar() + } else { + appStore.showTabBarFn() + } + + // 组件卸载时恢复TabBar显示 + return () => { + appStore.showTabBarFn() + } + }, [cityPopupOpen]) + // 打开城市选择 const handleOpenCityPicker = () => { setCityPopupOpen(true) diff --git a/src/pages/user/index.scss b/src/pages/user/index.scss index f9eb6a9..c361989 100644 --- a/src/pages/user/index.scss +++ b/src/pages/user/index.scss @@ -156,7 +156,7 @@ } .stat-label { - margin-top: 27px; + margin-top: 20px; font-size: 25rpx; color: #B8B8B8; } diff --git a/src/pagesUser/editProfile/index.config.ts b/src/pagesUser/editProfile/index.config.ts index bda938d..76c2c08 100644 --- a/src/pagesUser/editProfile/index.config.ts +++ b/src/pagesUser/editProfile/index.config.ts @@ -1,5 +1,8 @@ export default { - navigationBarTitleText: '编辑资料', - navigationBarBackgroundColor: '#FFFFFF', - navigationBarTextStyle: 'black', + navigationBarTitleText: '个人信息', + navigationBarBackgroundColor: '#121212', + // navigationBarTextStyle 同时控制标题和返回按钮的颜色,只能是 'black' 或 'white' + navigationBarTextStyle: 'white', + // 不可滚动 + disableScroll: true, } diff --git a/src/pagesUser/editProfile/index.scss b/src/pagesUser/editProfile/index.scss index 68cecc1..60571cb 100644 --- a/src/pagesUser/editProfile/index.scss +++ b/src/pagesUser/editProfile/index.scss @@ -1,5 +1,210 @@ .edit-profile-page { min-height: 100vh; - background-color: #F2F3F5; - padding: 30px; + background-color: #121212; + + // 自定义导航栏 + .custom-navbar { + height: 88px; + background-color: #121212; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 30px; + position: relative; + + .navbar-left { + width: 60px; + height: 100%; + display: flex; + align-items: center; + } + + .navbar-title { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + + text { + font-size: 33rpx; + color: #FFFFFF; + font-weight: 500; + } + } + + .navbar-right { + width: 60px; + } + } + + // 内容区域 + .content-area { + padding: 0 30px; + + // 头像区域 + .avatar-section { + width: 100%; + height: 166px; + margin: 100px 0 82px; + display: flex; + align-items: center; + justify-content: center; + + .avatar-wrapper { + position: relative; + width: 166px; + height: 166px; + + .avatar { + width: 100%; + height: 100%; + display: block; + } + + :global { + .taroify-image { + width: 100%; + height: 100%; + display: block; + } + + .taroify-image__img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + // 相机图标 + .camera-icon { + position: absolute; + right: 0; + bottom: 0; + width: 46px; + height: 46px; + background: #03C175; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4px solid #121212; + + .camera-img { + width: 24px; + height: 24px; + } + } + } + } + + // 表单区域 + .form-section { + .form-item { + height: 100px; + background-color: #21212A; + border-radius: 13px; + margin-bottom: 20px; + padding: 0 30px; + display: flex; + align-items: center; + justify-content: space-between; + + .form-label { + font-size: 31rpx; + color: #E6E6E6; + flex-shrink: 0; + } + + .form-input { + flex: 1; + text-align: right; + font-size: 31rpx; + color: #FFFFFF; + margin-left: 30px; + } + + .form-placeholder { + color: #666666; + font-size: 31rpx; + } + + .form-value { + display: flex; + align-items: center; + + text { + font-size: 31rpx; + color: #FFFFFF; + margin-right: 10px; + } + + .arrow-icon { + transform: rotate(180deg); + } + } + } + } + + // 保存按钮 + .save-button { + margin-top: 82px; + width: 100%; + height: 88px; + background-color: #03C175; + border-radius: 44px; + display: flex; + align-items: center; + justify-content: center; + font-size: 33rpx; + color: #FFFFFF; + font-weight: 500; + border: none; + + &::after { + border: none; + } + } + } + + // 头像选择菜单样式 + :global { + .taroify-action-sheet { + .taroify-action-sheet__action { + font-size: 32rpx; + color: #333333; + padding: 30rpx 0; + } + } + } + + .wechat-avatar-btn { + width: 100%; + background: transparent; + border: none; + font-size: 32rpx; + color: #333333; + padding: 30rpx 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0; + line-height: 1.4; + + &::after { + border: none; + } + + .avatar-emoji { + margin-right: 10rpx; + font-size: 36rpx; + } + } + + .avatar-action-item { + &.cancel-action { + color: #999999; + margin-top: 20rpx; + border-top: 1PX solid #EEEEEE; + } + } } diff --git a/src/pagesUser/editProfile/index.tsx b/src/pagesUser/editProfile/index.tsx index 2b91266..5268dd7 100644 --- a/src/pagesUser/editProfile/index.tsx +++ b/src/pagesUser/editProfile/index.tsx @@ -1,18 +1,231 @@ /** * 编辑个人信息页 */ -import { View, Text } from '@tarojs/components' -import { useLoad } from '@tarojs/taro' +import { View, Text, Image, Input, Button as WxButton } from '@tarojs/components' +import Taro, { useLoad } from '@tarojs/taro' +import { useState } from 'react' +import { Image as TaroifyImage, DatetimePicker, Popup, Button, ActionSheet } from '@taroify/core' +import { ArrowLeft } from '@taroify/icons' +import myAvatar from '../../assets/icon/my_avatar.png' import './index.scss' const EditProfile = () => { + const [nickname, setNickname] = useState('旋转跳跃的球圣') + const [birthday, setBirthday] = useState(new Date('2025-01-01')) + const [datePickerOpen, setDatePickerOpen] = useState(false) + const [avatarActionOpen, setAvatarActionOpen] = useState(false) + const [avatarUrl, setAvatarUrl] = useState(myAvatar) + useLoad(() => { console.log('编辑个人信息页面加载') }) + // 返回上一页 + const handleBack = () => { + Taro.navigateBack() + } + + // 点击头像,打开选择菜单 + const handleAvatarClick = () => { + setAvatarActionOpen(true) + } + + // 使用微信头像 + const handleUseWechatAvatar = () => { + // 注意:button open-type="chooseAvatar" 需要在 button 组件上使用 + // 这里先关闭 ActionSheet,让用户点击特殊的 button + setAvatarActionOpen(false) + // 实际使用时需要通过 button open-type="chooseAvatar" 触发 + Taro.showToast({ + title: '请使用下方按钮选择微信头像', + icon: 'none', + duration: 2000 + }) + } + + // 从相册选择 + const handleChooseFromAlbum = () => { + setAvatarActionOpen(false) + Taro.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['album'], + success: (res) => { + console.log('选择头像', res.tempFilePaths[0]) + setAvatarUrl(res.tempFilePaths[0]) + // TODO: 上传头像到服务器 + } + }) + } + + // 拍照 + const handleTakePhoto = () => { + setAvatarActionOpen(false) + Taro.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['camera'], + success: (res) => { + console.log('拍照', res.tempFilePaths[0]) + setAvatarUrl(res.tempFilePaths[0]) + // TODO: 上传头像到服务器 + } + }) + } + + // 获取微信头像回调 + const handleChooseAvatar = (e: any) => { + console.log('微信头像', e.detail.avatarUrl) + setAvatarUrl(e.detail.avatarUrl) + // TODO: 上传头像到服务器 + } + + // 格式化日期显示 + const formatDate = (date: Date) => { + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } + + // 确认选择日期 + const handleDateConfirm = (newValue: any) => { + setBirthday(newValue) + setDatePickerOpen(false) + } + + // 保存 + const handleSave = () => { + Taro.showToast({ + title: '保存成功', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + Taro.navigateBack() + }, 2000) + } + return ( - 编辑个人信息 + {/* 自定义导航栏 */} + {/* + + + + + 个人信息 + + + */} + + {/* 内容区域 */} + + {/* 头像区域 */} + + + + {/* 相机图标 */} + + + + + + + {/* 表单区域 */} + + {/* 昵称 */} + + 昵称 + setNickname(e.detail.value)} + placeholder="请输入昵称" + placeholderClass="form-placeholder" + /> + + + {/* 生日 */} + setDatePickerOpen(true)}> + 生日 + + {formatDate(birthday)} + + + + + + {/* 保存按钮 */} + + + + {/* 日期选择器 */} + setDatePickerOpen(false)} + > + setDatePickerOpen(false)} + onConfirm={handleDateConfirm} + > + {/* + */} + + + + {/* 头像选择菜单 */} + {}} + onClose={() => setAvatarActionOpen(false)} + > + {/* 使用微信头像 - 需要特殊处理 */} + + 🤵 + 用微信头像 + + + + 从相册选择 + + + + 拍照 + + + + 取消 + + ) } diff --git a/src/store/app.ts b/src/store/app.ts new file mode 100644 index 0000000..c74e9c5 --- /dev/null +++ b/src/store/app.ts @@ -0,0 +1,30 @@ +/** + * App Store - 全局应用状态 + */ +import { makeAutoObservable } from 'mobx' + +class AppStore { + // 控制 TabBar 显示/隐藏 + showTabBar = true + + constructor() { + makeAutoObservable(this) + } + + // 设置 TabBar 显示状态 + setShowTabBar(show: boolean) { + this.showTabBar = show + } + + // 隐藏 TabBar(弹窗打开时) + hideTabBar() { + this.showTabBar = false + } + + // 显示 TabBar(弹窗关闭时) + showTabBarFn() { + this.showTabBar = true + } +} + +export const appStore = new AppStore() diff --git a/src/store/index.ts b/src/store/index.ts index af35042..4e94713 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,11 +2,14 @@ * Store 统一导出 - MobX */ import { userStore as _userStore } from './user' +import { appStore as _appStore } from './app' // 重新导出 export const userStore = _userStore +export const appStore = _appStore // 导出所有 store 的集合 export const stores = { userStore: _userStore, + appStore: _appStore, }