Browse Source

我的页面, 我的页面编辑页面,我的页面分包

master
DU 6 months ago
parent
commit
bfb1c4f2a9
  1. 12
      .cursorrules
  2. 2
      .gitignore
  3. BIN
      src/assets/icon/phototake.png
  4. 122
      src/custom-tab-bar/index.scss
  5. 6
      src/custom-tab-bar/index.tsx
  6. 6
      src/pages/index/index.scss
  7. 8
      src/pages/message/index.scss
  8. 7
      src/pages/store/index.scss
  9. 17
      src/pages/store/index.tsx
  10. 2
      src/pages/user/index.scss
  11. 9
      src/pagesUser/editProfile/index.config.ts
  12. 209
      src/pagesUser/editProfile/index.scss
  13. 219
      src/pagesUser/editProfile/index.tsx
  14. 30
      src/store/app.ts
  15. 3
      src/store/index.ts

12
.cursorrules

@ -65,8 +65,9 @@ import { HomeOutlined } from '@taroify/icons' @@ -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 @@ -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`

2
.gitignore vendored

@ -23,3 +23,5 @@ Thumbs.db @@ -23,3 +23,5 @@ Thumbs.db
# 环境变量
.env
.env.local
docs/

BIN
src/assets/icon/phototake.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

122
src/custom-tab-bar/index.scss

@ -1,9 +1,9 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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;
height: calc(129px + env(safe-area-inset-bottom));
}
.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;
}
}
.tab-item.active .tab-content .tab-text {
color: #ffffff;
.tab-bar-content {
height: calc(129px + env(safe-area-inset-bottom));
}
}
}

6
src/custom-tab-bar/index.tsx

@ -6,6 +6,7 @@ import { CoverView, CoverImage } from '@tarojs/components' @@ -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(() => { @@ -59,6 +60,11 @@ const CustomTabBar = observer(() => {
return isActive ? icons.active : icons.normal
}
// 如果 TabBar 被隐藏,则不渲染
if (!appStore.showTabBar) {
return null
}
return (
<CoverView className="custom-tab-bar">
{/* 背景白色层 */}

6
src/pages/index/index.scss

@ -4,10 +4,8 @@ @@ -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;

8
src/pages/message/index.scss

@ -45,13 +45,9 @@ @@ -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;
}
// 空状态

7
src/pages/store/index.scss

@ -4,10 +4,8 @@ @@ -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 @@ @@ -245,6 +243,7 @@
}
/* 城市选择弹窗 */
/* 城市选择弹窗样式 */
.city-picker {
background: #fff;
border-radius: 20px 20px 0 0;

17
src/pages/store/index.tsx

@ -4,10 +4,11 @@ @@ -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(() => { @@ -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)

2
src/pages/user/index.scss

@ -156,7 +156,7 @@ @@ -156,7 +156,7 @@
}
.stat-label {
margin-top: 27px;
margin-top: 20px;
font-size: 25rpx;
color: #B8B8B8;
}

9
src/pagesUser/editProfile/index.config.ts

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
export default {
navigationBarTitleText: '编辑资料',
navigationBarBackgroundColor: '#FFFFFF',
navigationBarTextStyle: 'black',
navigationBarTitleText: '个人信息',
navigationBarBackgroundColor: '#121212',
// navigationBarTextStyle 同时控制标题和返回按钮的颜色,只能是 'black' 或 'white'
navigationBarTextStyle: 'white',
// 不可滚动
disableScroll: true,
}

209
src/pagesUser/editProfile/index.scss

@ -1,5 +1,210 @@ @@ -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;
}
}
}

219
src/pagesUser/editProfile/index.tsx

@ -1,18 +1,231 @@ @@ -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 (
<View className="edit-profile-page">
<Text></Text>
{/* 自定义导航栏 */}
{/* <View className="custom-navbar">
<View className="navbar-left" onClick={handleBack}>
<ArrowLeft size={20} color="#FFFFFF" />
</View>
<View className="navbar-title">
<Text></Text>
</View>
<View className="navbar-right" />
</View> */}
{/* 内容区域 */}
<View className="content-area">
{/* 头像区域 */}
<View className="avatar-section" onClick={handleAvatarClick}>
<View className="avatar-wrapper">
<TaroifyImage
className="avatar"
src={avatarUrl}
round
mode="aspectFill"
/>
{/* 相机图标 */}
<View className="camera-icon">
<Image
className="camera-img"
src={require('../../assets/icon/phototake.png')}
mode="widthFix"
/>
</View>
</View>
</View>
{/* 表单区域 */}
<View className="form-section">
{/* 昵称 */}
<View className="form-item">
<Text className="form-label"></Text>
<Input
className="form-input"
value={nickname}
onInput={(e) => setNickname(e.detail.value)}
placeholder="请输入昵称"
placeholderClass="form-placeholder"
/>
</View>
{/* 生日 */}
<View className="form-item" onClick={() => setDatePickerOpen(true)}>
<Text className="form-label"></Text>
<View className="form-value">
<Text>{formatDate(birthday)}</Text>
<ArrowLeft className="arrow-icon" size={16} color="#999" />
</View>
</View>
</View>
{/* 保存按钮 */}
<Button className="save-button" onClick={handleSave}>
</Button>
</View>
{/* 日期选择器 */}
<Popup
open={datePickerOpen}
placement="bottom"
onClose={() => setDatePickerOpen(false)}
>
<DatetimePicker
type="date"
value={birthday}
onChange={handleDateConfirm}
onCancel={() => setDatePickerOpen(false)}
onConfirm={handleDateConfirm}
>
{/* <Button></Button>
<Button></Button> */}
</DatetimePicker>
</Popup>
{/* 头像选择菜单 */}
<ActionSheet
open={avatarActionOpen}
onSelect={() => {}}
onClose={() => setAvatarActionOpen(false)}
>
{/* 使用微信头像 - 需要特殊处理 */}
<WxButton
className="avatar-action-item wechat-avatar-btn"
openType="chooseAvatar"
onChooseAvatar={handleChooseAvatar}
>
<Text className="avatar-emoji">🤵</Text>
<Text></Text>
</WxButton>
<ActionSheet.Action
className="avatar-action-item"
onClick={handleChooseFromAlbum}
>
</ActionSheet.Action>
<ActionSheet.Action
className="avatar-action-item"
onClick={handleTakePhoto}
>
</ActionSheet.Action>
<ActionSheet.Action
className="avatar-action-item cancel-action"
>
</ActionSheet.Action>
</ActionSheet>
</View>
)
}

30
src/store/app.ts

@ -0,0 +1,30 @@ @@ -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()

3
src/store/index.ts

@ -2,11 +2,14 @@ @@ -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,
}

Loading…
Cancel
Save