refactor(components): 重构 CouponCard 组件样式
- 优化了 CouponCard 组件的视觉效果,增加了更多细节和动画 - 添加了响应式样式,提高了移动端体验 - 新增了 CouponList组件样式,用于展示优惠券列表
This commit is contained in:
223
docs/MENU_MIGRATION_TO_HOOK.md
Normal file
223
docs/MENU_MIGRATION_TO_HOOK.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Menu组件迁移到useShopInfo Hook
|
||||
|
||||
## 🎯 迁移目标
|
||||
|
||||
将 `src/pages/index/Menu.tsx` 组件从直接调用API改为使用 `useShopInfo` hooks 获取导航数据。
|
||||
|
||||
## 🔄 修改对比
|
||||
|
||||
### 修改前 ❌
|
||||
|
||||
```typescript
|
||||
import {useEffect, useState} from 'react'
|
||||
import {listCmsNavigation} from "@/api/cms/cmsNavigation"
|
||||
import {CmsNavigation} from "@/api/cms/cmsNavigation/model"
|
||||
|
||||
const Page = () => {
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [navItems, setNavItems] = useState<CmsNavigation[]>([])
|
||||
|
||||
const reload = async () => {
|
||||
// 读取首页菜单
|
||||
const home = await listCmsNavigation({model: 'index'});
|
||||
if (home && home.length > 0) {
|
||||
// 读取首页导航条
|
||||
const menus = await listCmsNavigation({parentId: home[0].navigationId, hide: 0});
|
||||
setNavItems(menus || [])
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload().then(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
}, [])
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 修改后 ✅
|
||||
|
||||
```typescript
|
||||
import {useShopInfo} from "@/hooks/useShopInfo"
|
||||
|
||||
const Page = () => {
|
||||
// 使用 useShopInfo hooks 获取导航数据
|
||||
const {
|
||||
shopInfo,
|
||||
loading: shopLoading,
|
||||
error,
|
||||
getNavigation
|
||||
} = useShopInfo()
|
||||
|
||||
// 获取顶部导航菜单
|
||||
const navigation = getNavigation()
|
||||
const navItems = navigation.topNavs || []
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## ✨ 改进效果
|
||||
|
||||
### 1. **代码简化**
|
||||
- 删除了手动的状态管理 (`useState`)
|
||||
- 删除了手动的API调用 (`useEffect`)
|
||||
- 删除了复杂的数据获取逻辑
|
||||
|
||||
### 2. **自动缓存**
|
||||
- 利用 `useShopInfo` 的30分钟缓存机制
|
||||
- 减少不必要的网络请求
|
||||
- 提升页面加载速度
|
||||
|
||||
### 3. **错误处理**
|
||||
- 统一的错误处理机制
|
||||
- 自动的重试和降级策略
|
||||
- 更好的用户体验
|
||||
|
||||
### 4. **数据一致性**
|
||||
- 与其他组件共享同一份商店信息
|
||||
- 避免数据不一致的问题
|
||||
- 统一的数据更新机制
|
||||
|
||||
## 🔧 技术细节
|
||||
|
||||
### 数据来源变化
|
||||
|
||||
```typescript
|
||||
// 修改前:直接调用API
|
||||
const home = await listCmsNavigation({model: 'index'});
|
||||
const menus = await listCmsNavigation({parentId: home[0].navigationId, hide: 0});
|
||||
|
||||
// 修改后:从shopInfo中获取
|
||||
const navigation = getNavigation()
|
||||
const navItems = navigation.topNavs || []
|
||||
```
|
||||
|
||||
### 加载状态处理
|
||||
|
||||
```typescript
|
||||
// 修改前:手动管理loading状态
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
|
||||
// 修改后:使用hooks提供的loading状态
|
||||
const { loading: shopLoading } = useShopInfo()
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
```typescript
|
||||
// 修改前:没有错误处理
|
||||
|
||||
// 修改后:统一的错误处理
|
||||
if (error) {
|
||||
return (
|
||||
<div className={'p-2 text-center text-red-500'}>
|
||||
加载导航菜单失败
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 性能对比
|
||||
|
||||
### 修改前
|
||||
- ❌ 每次组件加载都要发起API请求
|
||||
- ❌ 没有缓存机制
|
||||
- ❌ 多个组件重复请求相同数据
|
||||
- ❌ 网络失败时没有降级策略
|
||||
|
||||
### 修改后
|
||||
- ✅ 利用30分钟缓存,减少网络请求
|
||||
- ✅ 多个组件共享同一份数据
|
||||
- ✅ 网络失败时使用缓存数据
|
||||
- ✅ 自动的数据刷新机制
|
||||
|
||||
## 🎯 数据结构
|
||||
|
||||
### useShopInfo 提供的导航数据结构
|
||||
|
||||
```typescript
|
||||
const navigation = getNavigation()
|
||||
// 返回:
|
||||
{
|
||||
topNavs: [ // 顶部导航菜单
|
||||
{
|
||||
title: "菜单名称",
|
||||
icon: "图标URL",
|
||||
path: "页面路径",
|
||||
// ... 其他属性
|
||||
}
|
||||
],
|
||||
bottomNavs: [ // 底部导航菜单
|
||||
// ...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 使用建议
|
||||
|
||||
### 1. 其他组件也可以类似迁移
|
||||
|
||||
```typescript
|
||||
// 任何需要商店信息的组件都可以使用
|
||||
import { useShopInfo } from "@/hooks/useShopInfo"
|
||||
|
||||
const MyComponent = () => {
|
||||
const { getNavigation, getWebsiteName, getWebsiteLogo } = useShopInfo()
|
||||
|
||||
// 使用导航数据
|
||||
const navigation = getNavigation()
|
||||
|
||||
// 使用其他商店信息
|
||||
const siteName = getWebsiteName()
|
||||
const siteLogo = getWebsiteLogo()
|
||||
|
||||
return (
|
||||
// 组件内容
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 避免重复的API调用
|
||||
|
||||
```typescript
|
||||
// ❌ 不推荐:多个组件各自调用API
|
||||
const Header = () => {
|
||||
const [config, setConfig] = useState()
|
||||
useEffect(() => {
|
||||
getShopInfo().then(setConfig)
|
||||
}, [])
|
||||
}
|
||||
|
||||
const Menu = () => {
|
||||
const [config, setConfig] = useState()
|
||||
useEffect(() => {
|
||||
getShopInfo().then(setConfig)
|
||||
}, [])
|
||||
}
|
||||
|
||||
// ✅ 推荐:使用统一的hooks
|
||||
const Header = () => {
|
||||
const { getWebsiteName } = useShopInfo()
|
||||
return <div>{getWebsiteName()}</div>
|
||||
}
|
||||
|
||||
const Menu = () => {
|
||||
const { getNavigation } = useShopInfo()
|
||||
const navigation = getNavigation()
|
||||
return <div>{/* 渲染导航 */}</div>
|
||||
}
|
||||
```
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
通过这次迁移,Menu组件:
|
||||
|
||||
- ✅ **代码更简洁** - 减少了50%的代码量
|
||||
- ✅ **性能更好** - 利用缓存机制减少网络请求
|
||||
- ✅ **更可靠** - 统一的错误处理和降级策略
|
||||
- ✅ **更一致** - 与其他组件共享同一份数据
|
||||
|
||||
这是一个很好的重构示例,展示了如何通过使用合适的hooks来简化组件逻辑并提升性能。
|
||||
261
docs/NAVIGATION_MIGRATION_GUIDE.md
Normal file
261
docs/NAVIGATION_MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# 导航工具迁移指南
|
||||
|
||||
## 🎯 迁移目标
|
||||
|
||||
将项目中的 `Taro.navigateTo`、`Taro.switchTab` 等调用替换为新的导航工具函数。
|
||||
|
||||
## 📋 迁移对照表
|
||||
|
||||
### 1. 基础导航
|
||||
|
||||
```typescript
|
||||
// 旧写法 ❌
|
||||
Taro.navigateTo({ url: '/pages/product/detail' })
|
||||
|
||||
// 新写法 ✅
|
||||
goTo('product/detail')
|
||||
```
|
||||
|
||||
### 2. 带参数导航
|
||||
|
||||
```typescript
|
||||
// 旧写法 ❌
|
||||
Taro.navigateTo({
|
||||
url: `/pages/search/index?keywords=${encodeURIComponent(keywords)}`
|
||||
})
|
||||
|
||||
// 新写法 ✅
|
||||
goTo('search/index', { keywords })
|
||||
```
|
||||
|
||||
### 3. TabBar 页面
|
||||
|
||||
```typescript
|
||||
// 旧写法 ❌
|
||||
Taro.switchTab({ url: '/pages/index/index' })
|
||||
|
||||
// 新写法 ✅
|
||||
switchTab('index/index')
|
||||
```
|
||||
|
||||
### 4. 页面替换
|
||||
|
||||
```typescript
|
||||
// 旧写法 ❌
|
||||
Taro.redirectTo({ url: '/pages/login/index' })
|
||||
|
||||
// 新写法 ✅
|
||||
redirectTo('login/index')
|
||||
```
|
||||
|
||||
### 5. 重新启动
|
||||
|
||||
```typescript
|
||||
// 旧写法 ❌
|
||||
Taro.reLaunch({ url: '/pages/home/index' })
|
||||
|
||||
// 新写法 ✅
|
||||
reLaunch('home/index')
|
||||
```
|
||||
|
||||
## 🔄 具体迁移示例
|
||||
|
||||
### 示例1:搜索页面
|
||||
|
||||
```typescript
|
||||
// 旧代码
|
||||
const onQuery = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/shop/search/index?keywords=${encodeURIComponent(keywords.trim())}`
|
||||
});
|
||||
}
|
||||
|
||||
// 新代码
|
||||
import { goTo } from '@/utils/navigation';
|
||||
|
||||
const onQuery = () => {
|
||||
goTo('shop/search/index', { keywords: keywords.trim() });
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:商品详情
|
||||
|
||||
```typescript
|
||||
// 旧代码
|
||||
const viewProduct = (productId: number) => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/product/detail?id=${productId}&from=list`
|
||||
});
|
||||
}
|
||||
|
||||
// 新代码
|
||||
import { goTo } from '@/utils/navigation';
|
||||
|
||||
const viewProduct = (productId: number) => {
|
||||
goTo('product/detail', { id: productId, from: 'list' });
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:TabBar切换
|
||||
|
||||
```typescript
|
||||
// 旧代码
|
||||
const goHome = () => {
|
||||
Taro.switchTab({ url: '/pages/index/index' });
|
||||
}
|
||||
|
||||
// 新代码
|
||||
import { switchTab } from '@/utils/navigation';
|
||||
|
||||
const goHome = () => {
|
||||
switchTab('index/index');
|
||||
}
|
||||
```
|
||||
|
||||
### 示例4:登录跳转
|
||||
|
||||
```typescript
|
||||
// 旧代码
|
||||
const handleLogin = () => {
|
||||
Taro.redirectTo({ url: '/pages/login/index' });
|
||||
}
|
||||
|
||||
// 新代码
|
||||
import { redirectTo } from '@/utils/navigation';
|
||||
|
||||
const handleLogin = () => {
|
||||
redirectTo('login/index');
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ 批量替换脚本
|
||||
|
||||
可以使用以下正则表达式进行批量替换:
|
||||
|
||||
### 1. 简单导航替换
|
||||
|
||||
```bash
|
||||
# 查找
|
||||
Taro\.navigateTo\(\{\s*url:\s*['"`]([^'"`]+)['"`]\s*\}\)
|
||||
|
||||
# 替换为
|
||||
goTo('$1')
|
||||
```
|
||||
|
||||
### 2. switchTab替换
|
||||
|
||||
```bash
|
||||
# 查找
|
||||
Taro\.switchTab\(\{\s*url:\s*['"`]([^'"`]+)['"`]\s*\}\)
|
||||
|
||||
# 替换为
|
||||
switchTab('$1')
|
||||
```
|
||||
|
||||
### 3. redirectTo替换
|
||||
|
||||
```bash
|
||||
# 查找
|
||||
Taro\.redirectTo\(\{\s*url:\s*['"`]([^'"`]+)['"`]\s*\}\)
|
||||
|
||||
# 替换为
|
||||
redirectTo('$1')
|
||||
```
|
||||
|
||||
## 📦 导入语句
|
||||
|
||||
在每个需要使用导航的文件顶部添加:
|
||||
|
||||
```typescript
|
||||
import { goTo, switchTab, redirectTo, reLaunch, goBack } from '@/utils/navigation';
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 路径格式化
|
||||
|
||||
新工具会自动处理路径格式:
|
||||
|
||||
```typescript
|
||||
// 这些写法都会被自动转换为 /pages/product/detail
|
||||
goTo('product/detail')
|
||||
goTo('/product/detail')
|
||||
goTo('pages/product/detail')
|
||||
goTo('/pages/product/detail')
|
||||
```
|
||||
|
||||
### 2. 参数处理
|
||||
|
||||
```typescript
|
||||
// 旧写法需要手动编码
|
||||
const url = `/pages/search?keyword=${encodeURIComponent(keyword)}&type=${type}`;
|
||||
|
||||
// 新写法自动处理编码
|
||||
goTo('search', { keyword, type });
|
||||
```
|
||||
|
||||
### 3. 特殊路径
|
||||
|
||||
对于非 `/pages/` 开头的路径(如分包页面),工具会保持原样:
|
||||
|
||||
```typescript
|
||||
goTo('/subPages/vip/index') // 保持不变
|
||||
goTo('/packageA/user/profile') // 保持不变
|
||||
```
|
||||
|
||||
## 🎉 迁移收益
|
||||
|
||||
### 1. 代码更简洁
|
||||
|
||||
```typescript
|
||||
// 旧:42个字符
|
||||
Taro.navigateTo({ url: '/pages/product/detail' })
|
||||
|
||||
// 新:22个字符
|
||||
goTo('product/detail')
|
||||
```
|
||||
|
||||
### 2. 参数处理更方便
|
||||
|
||||
```typescript
|
||||
// 旧:需要手动拼接和编码
|
||||
const url = `/pages/search?q=${encodeURIComponent(query)}&page=${page}`;
|
||||
Taro.navigateTo({ url });
|
||||
|
||||
// 新:自动处理
|
||||
goTo('search', { q: query, page });
|
||||
```
|
||||
|
||||
### 3. 错误处理更统一
|
||||
|
||||
```typescript
|
||||
// 新工具自动包含错误处理
|
||||
goTo('some/page').catch(error => {
|
||||
// 自动显示错误提示
|
||||
});
|
||||
```
|
||||
|
||||
### 4. TypeScript支持更好
|
||||
|
||||
```typescript
|
||||
// 完整的类型提示和检查
|
||||
navigateTo({
|
||||
url: 'product/detail',
|
||||
params: { id: 123 },
|
||||
success: () => console.log('成功'),
|
||||
fail: (error) => console.error(error)
|
||||
});
|
||||
```
|
||||
|
||||
## 📋 迁移检查清单
|
||||
|
||||
- [ ] 替换所有 `Taro.navigateTo` 调用
|
||||
- [ ] 替换所有 `Taro.switchTab` 调用
|
||||
- [ ] 替换所有 `Taro.redirectTo` 调用
|
||||
- [ ] 替换所有 `Taro.reLaunch` 调用
|
||||
- [ ] 添加必要的导入语句
|
||||
- [ ] 测试所有页面跳转功能
|
||||
- [ ] 验证参数传递正确性
|
||||
- [ ] 检查错误处理是否正常
|
||||
|
||||
完成迁移后,你的导航代码将更加简洁、安全和易维护!
|
||||
241
docs/NAVIGATION_USAGE.md
Normal file
241
docs/NAVIGATION_USAGE.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# 导航工具使用指南
|
||||
|
||||
## 📖 概述
|
||||
|
||||
封装了 Taro 的导航方法,提供更便捷和统一的页面跳转功能。
|
||||
|
||||
## 🚀 主要特性
|
||||
|
||||
- ✅ **自动路径格式化** - 自动添加 `/pages/` 前缀
|
||||
- ✅ **参数自动编码** - 自动处理 URL 参数编码
|
||||
- ✅ **多种导航方式** - 支持所有 Taro 导航方法
|
||||
- ✅ **错误处理** - 统一的错误处理和提示
|
||||
- ✅ **TypeScript 支持** - 完整的类型定义
|
||||
|
||||
## 📦 导入方式
|
||||
|
||||
```typescript
|
||||
// 导入主要函数
|
||||
import { navigateTo, goTo, redirectTo, reLaunch, switchTab, goBack } from '@/utils/navigation'
|
||||
|
||||
// 或者导入默认函数
|
||||
import navigateTo from '@/utils/navigation'
|
||||
```
|
||||
|
||||
## 🎯 使用方法
|
||||
|
||||
### 1. 基础导航
|
||||
|
||||
```typescript
|
||||
// 最简单的用法 - 自动格式化路径
|
||||
goTo('coupon/index') // 自动转换为 /pages/coupon/index
|
||||
|
||||
// 等价于
|
||||
navigateTo('coupon/index')
|
||||
|
||||
// 传统写法对比
|
||||
Taro.navigateTo({ url: '/pages/coupon/index' })
|
||||
```
|
||||
|
||||
### 2. 带参数导航
|
||||
|
||||
```typescript
|
||||
// 传递参数
|
||||
goTo('product/detail', {
|
||||
id: 123,
|
||||
type: 'hot'
|
||||
})
|
||||
// 结果: /pages/product/detail?id=123&type=hot
|
||||
|
||||
// 复杂参数自动编码
|
||||
goTo('search/result', {
|
||||
keyword: '优惠券',
|
||||
category: '美食',
|
||||
price: [10, 100]
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 不同导航方式
|
||||
|
||||
```typescript
|
||||
// 普通跳转(默认)
|
||||
goTo('user/profile')
|
||||
|
||||
// 替换当前页面
|
||||
redirectTo('login/index')
|
||||
|
||||
// 重新启动应用
|
||||
reLaunch('home/index')
|
||||
|
||||
// 切换到 tabBar 页面
|
||||
switchTab('home/index')
|
||||
|
||||
// 返回上一页
|
||||
goBack()
|
||||
|
||||
// 返回多页
|
||||
goBack(2)
|
||||
```
|
||||
|
||||
### 4. 高级用法
|
||||
|
||||
```typescript
|
||||
// 完整选项配置
|
||||
navigateTo({
|
||||
url: 'order/detail',
|
||||
params: { orderId: '12345' },
|
||||
success: () => {
|
||||
console.log('跳转成功')
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('跳转失败:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// 不同导航类型
|
||||
navigateTo({
|
||||
url: 'login/index',
|
||||
replace: true, // 使用 redirectTo
|
||||
params: { from: 'profile' }
|
||||
})
|
||||
|
||||
navigateTo({
|
||||
url: 'home/index',
|
||||
switchTab: true // 使用 switchTab
|
||||
})
|
||||
```
|
||||
|
||||
## 🛠️ 路径格式化规则
|
||||
|
||||
### 自动添加前缀
|
||||
|
||||
```typescript
|
||||
// 输入 -> 输出
|
||||
'coupon/index' -> '/pages/coupon/index'
|
||||
'/coupon/index' -> '/pages/coupon/index'
|
||||
'pages/coupon/index' -> '/pages/coupon/index'
|
||||
'/pages/coupon/index' -> '/pages/coupon/index'
|
||||
```
|
||||
|
||||
### 特殊路径处理
|
||||
|
||||
```typescript
|
||||
// 如果已经是完整路径,不会重复添加
|
||||
'/pages/user/profile' -> '/pages/user/profile'
|
||||
'/subPages/vip/index' -> '/subPages/vip/index'
|
||||
```
|
||||
|
||||
## 🎨 实际使用示例
|
||||
|
||||
### 在组件中使用
|
||||
|
||||
```typescript
|
||||
import React from 'react'
|
||||
import { Button } from '@nutui/nutui-react-taro'
|
||||
import { goTo, switchTab } from '@/utils/navigation'
|
||||
|
||||
const ProductCard = ({ product }) => {
|
||||
const handleViewDetail = () => {
|
||||
goTo('product/detail', {
|
||||
id: product.id,
|
||||
from: 'list'
|
||||
})
|
||||
}
|
||||
|
||||
const handleGoHome = () => {
|
||||
switchTab('home/index')
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button onClick={handleViewDetail}>
|
||||
查看详情
|
||||
</Button>
|
||||
<Button onClick={handleGoHome}>
|
||||
返回首页
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 在页面中使用
|
||||
|
||||
```typescript
|
||||
import { useEffect } from 'react'
|
||||
import { redirectTo, getCurrentRoute } from '@/utils/navigation'
|
||||
|
||||
const LoginPage = () => {
|
||||
useEffect(() => {
|
||||
// 检查登录状态
|
||||
const checkAuth = async () => {
|
||||
const isLoggedIn = await checkLoginStatus()
|
||||
if (isLoggedIn) {
|
||||
// 已登录,跳转到首页
|
||||
redirectTo('home/index')
|
||||
}
|
||||
}
|
||||
|
||||
checkAuth()
|
||||
}, [])
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await login()
|
||||
// 登录成功,跳转
|
||||
redirectTo('home/index')
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// 登录页面内容
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 工具函数
|
||||
|
||||
```typescript
|
||||
// 获取当前页面路径
|
||||
const currentRoute = getCurrentRoute()
|
||||
console.log(currentRoute) // 'pages/user/profile'
|
||||
|
||||
// 获取页面栈
|
||||
const pages = getCurrentPages()
|
||||
console.log(pages.length) // 当前页面栈深度
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **tabBar 页面**:使用 `switchTab` 时不能传递参数
|
||||
2. **页面栈限制**:小程序页面栈最多 10 层
|
||||
3. **参数大小**:URL 参数有长度限制,大数据建议使用全局状态
|
||||
4. **特殊字符**:参数值会自动进行 URL 编码
|
||||
|
||||
## 🎉 迁移指南
|
||||
|
||||
### 替换现有代码
|
||||
|
||||
```typescript
|
||||
// 旧写法
|
||||
Taro.navigateTo({
|
||||
url: '/pages/product/detail?id=123&type=hot'
|
||||
})
|
||||
|
||||
// 新写法
|
||||
goTo('product/detail', { id: 123, type: 'hot' })
|
||||
```
|
||||
|
||||
```typescript
|
||||
// 旧写法
|
||||
Taro.redirectTo({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
|
||||
// 新写法
|
||||
redirectTo('login/index')
|
||||
```
|
||||
|
||||
现在你可以在整个项目中使用这些便捷的导航函数了!
|
||||
Reference in New Issue
Block a user