本指令规范了基于Taro + React + Shadui + Tailwind CSS的小程序页面开发流程,包含tabbar页和非tabbar页的创建标准和最佳实践,涵盖了认证、RPC调用、React Query v5使用等核心功能。
mini/src/components/ui
基于项目实际文件,当前小程序可用的shadui组件如下:
button.tsx)card.tsx)input.tsx)label.tsx)form.tsx)avatar-upload.tsx)carousel.tsx)image.tsx)navbar.tsx)tab-bar.tsx)TabBarLayout: 用于tabbar页面,包含底部导航
根据需求可扩展更多业务组件
import { Button } from '@/components/ui/button'
// 基础用法
<Button onClick={handleClick}>主要按钮</Button>
// 不同尺寸
<Button size="sm">小按钮</Button>
<Button size="md">中按钮</Button>
<Button size="lg">大按钮</Button>
// 不同样式
<Button variant="primary">主要按钮</Button>
<Button variant="secondary">次要按钮</Button>
<Button variant="outline">边框按钮</Button>
<Button variant="ghost">幽灵按钮</Button>
import { Input } from '@/components/ui/input'
// 基础用法
<Input placeholder="请输入内容" />
// 受控组件
<Input value={value} onChange={handleChange} />
// 不同类型
<Input type="text" placeholder="文本输入" />
<Input type="number" placeholder="数字输入" />
<Input type="password" placeholder="密码输入" />
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const formSchema = z.object({
username: z.string().min(2, '用户名至少2个字符'),
email: z.string().email('请输入有效的邮箱地址')
})
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: { username: '', email: '' }
})
<Form {...form}>
<FormField
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>用户名</FormLabel>
<FormControl>
<Input placeholder="请输入用户名" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form>
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
<Card>
<CardHeader>
<CardTitle>卡片标题</CardTitle>
</CardHeader>
<CardContent>
<Text>卡片内容</Text>
</CardContent>
</Card>
import { Navbar } from '@/components/ui/navbar'
// 基础用法
<Navbar title="页面标题" />
// 带返回按钮
<Navbar
title="页面标题"
leftIcon="i-heroicons-chevron-left-20-solid"
onClickLeft={() => Taro.navigateBack()}
/>
// 带右侧操作
<Navbar
title="页面标题"
rightIcon="i-heroicons-share-20-solid"
onClickRight={handleShare}
/>
// 实际页面使用示例
export function HomeCarousel() {
const bannerItems: CarouselItem[] = [
{
src: 'https://via.placeholder.com/750x400/3B82F6/FFFFFF?text=Banner+1',
title: '新品上市',
description: '最新款式,限时优惠',
link: '/pages/goods/new-arrival'
},
{
src: 'https://via.placeholder.com/750x400/EF4444/FFFFFF?text=Banner+2',
title: '限时秒杀',
description: '每日特价,不容错过',
link: '/pages/goods/flash-sale'
},
{
src: 'https://via.placeholder.com/750x400/10B981/FFFFFF?text=Banner+3',
title: '会员专享',
description: '会员专享折扣和福利',
link: '/pages/member/benefits'
}
]
const handleBannerClick = (item: CarouselItem, index: number) => {
if (item.link) {
// 使用Taro跳转
Taro.navigateTo({
url: item.link
})
}
}
return (
<View className="w-full">
<Carousel
items={bannerItems}
height={400}
autoplay={true}
interval={4000}
circular={true}
rounded="none"
onItemClick={handleBannerClick}
/>
</View>
)
}
特点:
TabBarLayout 布局组件mini/src/app.config.ts 中的 tabBar.listNavbar 顶部导航组件特点:
TabBarLayout,直接渲染内容Navbar 组件作为顶部导航# TabBar页面
mkdir -p mini/src/pages/[页面名称]
# 非TabBar页面
mkdir -p mini/src/pages/[页面名称]
// mini/src/pages/[页面名称]/index.tsx
import React from 'react'
import { View, Text } from '@tarojs/components'
import { TabBarLayout } from '@/layouts/tab-bar-layout'
import { Navbar } from '@/components/ui/navbar'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import './index.css'
const [页面名称]Page: React.FC = () => {
return (
<TabBarLayout activeKey="[对应tabBar.key]">
<Navbar
title="页面标题"
rightIcon="i-heroicons-[图标名称]-20-solid"
onClickRight={() => console.log('点击右上角')}
leftIcon=""
/>
<View className="px-4 py-4">
<Card>
<CardHeader>
<CardTitle>欢迎使用</CardTitle>
</CardHeader>
<CardContent>
<Text>这是一个使用shadui组件的TabBar页面</Text>
<Button className="mt-4">开始使用</Button>
</CardContent>
</Card>
</View>
</TabBarLayout>
)
}
export default [页面名称]Page
// mini/src/pages/[页面名称]/index.tsx
import { View } from '@tarojs/components'
import { useEffect } from 'react'
import Taro from '@tarojs/taro'
import { Navbar } from '@/components/ui/navbar'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import './index.css'
export default function [页面名称]() {
useEffect(() => {
Taro.setNavigationBarTitle({
title: '页面标题'
})
}, [])
return (
<View className="min-h-screen bg-gray-50">
<Navbar
title="页面标题"
backgroundColor="bg-transparent"
textColor="text-gray-900"
border={false}
/>
<View className="px-6 py-4">
<Card>
<CardContent>
<Text>这是一个使用shadui组件的非TabBar页面</Text>
<Button className="mt-4">返回</Button>
</CardContent>
</Card>
</View>
</View>
)
}
// mini/src/pages/[页面名称]/index.config.ts
export default definePageConfig({
navigationBarTitleText: '页面标题',
enablePullDownRefresh: true,
backgroundTextStyle: 'dark',
navigationBarBackgroundColor: '#ffffff',
navigationBarTextStyle: 'black'
})
统一使用tailwindcss类,index.css为空即可
/* mini/src/pages/[页面名称]/index.css */
// mini/src/pages/[需要认证的页面]/index.tsx
import { View, Text } from '@tarojs/components'
import { useEffect } from 'react'
import { useAuth } from '@/utils/auth'
import Taro from '@tarojs/taro'
import { Navbar } from '@/components/ui/navbar'
import { Card } from '@/components/ui/card'
export default function ProtectedPage() {
const { user, isLoading, isLoggedIn } = useAuth()
useEffect(() => {
if (!isLoading && !isLoggedIn) {
Taro.navigateTo({ url: '/pages/login/index' })
}
}, [isLoading, isLoggedIn])
if (isLoading) {
return (
<View className="flex-1 flex items-center justify-center">
<View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
</View>
)
}
if (!user) return null
return (
<View className="min-h-screen bg-gray-50">
<Navbar title="受保护页面" leftIcon="" />
<View className="px-4 py-4">
<Text>欢迎, {user.username}</Text>
</View>
</View>
)
}
// mini/src/pages/[数据展示页面]/index.tsx
import { View, ScrollView } from '@tarojs/components'
import { useQuery } from '@tanstack/react-query'
import { userClient } from '@/api'
import { InferResponseType } from 'hono'
import Taro from '@tarojs/taro'
type UserListResponse = InferResponseType<typeof userClient.$get, 200>
export default function UserListPage() {
const { data, isLoading, error } = useQuery<UserListResponse>({
queryKey: ['users'],
queryFn: async () => {
const response = await userClient.$get({})
if (response.status !== 200) {
throw new Error('获取用户列表失败')
}
return response.json()
},
staleTime: 5 * 60 * 1000, // 5分钟
})
if (isLoading) {
return (
<View className="flex-1 flex items-center justify-center">
<View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
</View>
)
}
if (error) {
return (
<View className="flex-1 flex items-center justify-center">
<Text className="text-red-500">{error.message}</Text>
</View>
)
}
return (
<ScrollView className="h-screen">
<Navbar title="用户列表" leftIcon="" />
<View className="px-4 py-4">
{data?.data.map(user => (
<View key={user.id} className="bg-white rounded-lg p-4 mb-3">
<Text>{user.username}</Text>
</View>
))}
</View>
</ScrollView>
)
}
// mini/src/pages/[表单页面]/index.tsx
import { View } from '@tarojs/components'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useMutation } from '@tanstack/react-query'
import { userClient } from '@/api'
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import Taro from '@tarojs/taro'
const formSchema = z.object({
username: z.string().min(3, '用户名至少3个字符'),
email: z.string().email('请输入有效的邮箱地址'),
phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号')
})
type FormData = z.infer<typeof formSchema>
export default function CreateUserPage() {
const [loading, setLoading] = useState(false)
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
email: '',
phone: ''
}
})
const mutation = useMutation({
mutationFn: async (data: FormData) => {
const response = await userClient.$post({ json: data })
if (response.status !== 201) {
throw new Error('创建用户失败')
}
return response.json()
},
onSuccess: () => {
Taro.showToast({
title: '创建成功',
icon: 'success'
})
Taro.navigateBack()
},
onError: (error) => {
Taro.showToast({
title: error.message || '创建失败',
icon: 'none'
})
}
})
const onSubmit = async (data: FormData) => {
setLoading(true)
try {
await mutation.mutateAsync(data)
} finally {
setLoading(false)
}
}
return (
<View className="min-h-screen bg-gray-50">
<Navbar title="创建用户" leftIcon="" />
<View className="px-4 py-4">
<Form {...form}>
<View className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>用户名</FormLabel>
<FormControl>
<Input placeholder="请输入用户名" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
className="w-full"
onClick={form.handleSubmit(onSubmit)}
disabled={loading}
>
{loading ? '创建中...' : '创建用户'}
</Button>
</View>
</Form>
</View>
</View>
)
}
import { useAuth } from '@/utils/auth'
// 在页面或组件中使用
const {
user, // 当前用户信息
login, // 登录函数
logout, // 登出函数
register, // 注册函数
updateUser, // 更新用户信息
isLoading, // 加载状态
isLoggedIn // 是否已登录
} = useAuth()
// 使用示例
const handleLogin = async (formData) => {
try {
await login(formData)
Taro.switchTab({ url: '/pages/index/index' })
} catch (error) {
console.error('登录失败:', error)
}
}
// 在需要认证的页面顶部
const { user, isLoading, isLoggedIn } = useAuth()
useEffect(() => {
if (!isLoading && !isLoggedIn) {
Taro.navigateTo({ url: '/pages/login/index' })
}
}, [isLoading, isLoggedIn])
// 或者使用路由守卫模式
// 从api.ts导入对应的客户端
import { authClient, userClient, fileClient } from '@/api'
import { InferResponseType, InferRequestType } from 'hono'
// 响应类型提取
type UserResponse = InferResponseType<typeof userClient.$get, 200>
type UserDetailResponse = InferResponseType<typeof userClient[':id']['$get'], 200>
// 请求类型提取
type CreateUserRequest = InferRequestType<typeof userClient.$post>['json']
type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json']
// GET请求 - 列表
const response = await userClient.$get({
query: {
page: 1,
pageSize: 10,
keyword: 'search'
}
})
// GET请求 - 单条
const response = await userClient[':id'].$get({
param: { id: userId }
})
// POST请求
const response = await userClient.$post({
json: {
username: 'newuser',
email: 'user@example.com'
}
})
// PUT请求
const response = await userClient[':id'].$put({
param: { id: userId },
json: { username: 'updated' }
})
// DELETE请求
const response = await userClient[':id'].$delete({
param: { id: userId }
})
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['users', page, keyword], // 唯一的查询键
queryFn: async () => {
const response = await userClient.$get({
query: { page, pageSize: 10, keyword }
})
if (response.status !== 200) {
throw new Error('获取数据失败')
}
return response.json()
},
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 10 * 60 * 1000, // 10分钟
retry: 3, // 重试3次
enabled: !!keyword, // 条件查询
})
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: async (data: CreateUserRequest) => {
const response = await userClient.$post({ json: data })
if (response.status !== 201) {
throw new Error('创建失败')
}
return response.json()
},
onSuccess: () => {
// 成功后刷新相关查询
queryClient.invalidateQueries({ queryKey: ['users'] })
Taro.showToast({ title: '创建成功', icon: 'success' })
},
onError: (error) => {
Taro.showToast({
title: error.message || '操作失败',
icon: 'none'
})
}
})
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: async (id: number) => {
const response = await deliveryAddressClient[':id'].$delete({
param: { id }
})
if (response.status !== 204) {
throw new Error('删除地址失败')
}
return response.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['delivery-addresses'] })
Taro.showToast({
title: '删除成功',
icon: 'success'
})
},
onError: (error) => {
Taro.showToast({
title: error.message || '删除失败',
icon: 'none'
})
}
})
const useUserList = (page: number, pageSize: number = 10) => {
return useQuery({
queryKey: ['users', page, pageSize],
queryFn: async () => {
const response = await userClient.$get({
query: { page, pageSize }
})
return response.json()
},
keepPreviousData: true, // 保持上一页数据
})
}
import { useInfiniteQuery } from '@tanstack/react-query'
const useInfiniteUserList = (keyword?: string) => {
return useInfiniteQuery({
queryKey: ['users-infinite', keyword],
queryFn: async ({ pageParam = 1 }) => {
const response = await userClient.$get({
query: {
page: pageParam,
pageSize: 10,
keyword
}
})
if (response.status !== 200) {
throw new Error('获取用户列表失败')
}
return response.json()
},
getNextPageParam: (lastPage, allPages) => {
const totalPages = Math.ceil(lastPage.pagination.total / lastPage.pagination.pageSize)
const nextPage = allPages.length + 1
return nextPage <= totalPages ? nextPage : undefined
},
staleTime: 5 * 60 * 1000,
})
}
// 使用示例
const {
data,
isLoading,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch
} = useInfiniteUserList(searchKeyword)
// 合并所有分页数据
const allUsers = data?.pages.flatMap(page => page.data) || []
// 触底加载更多处理
const handleScrollToLower = () => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}
// mini/src/pages/[无限滚动列表]/index.tsx
import { View, ScrollView } from '@tarojs/components'
import { useInfiniteQuery } from '@tanstack/react-query'
import { goodsClient } from '@/api'
import { InferResponseType } from 'hono'
import Taro from '@tarojs/taro'
type GoodsResponse = InferResponseType<typeof goodsClient.$get, 200>
export default function InfiniteGoodsList() {
const [searchKeyword, setSearchKeyword] = useState('')
const {
data,
isLoading,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch
} = useInfiniteQuery({
queryKey: ['goods-infinite', searchKeyword],
queryFn: async ({ pageParam = 1 }) => {
const response = await goodsClient.$get({
query: {
page: pageParam,
pageSize: 10,
keyword: searchKeyword
}
})
if (response.status !== 200) {
throw new Error('获取商品失败')
}
return response.json()
},
getNextPageParam: (lastPage) => {
const { pagination } = lastPage
const totalPages = Math.ceil(pagination.total / pagination.pageSize)
return pagination.current < totalPages ? pagination.current + 1 : undefined
},
staleTime: 5 * 60 * 1000,
initialPageParam: 1,
})
// 合并所有分页数据
const allGoods = data?.pages.flatMap(page => page.data) || []
// 触底加载更多
const handleScrollToLower = () => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}
// 下拉刷新
const onPullDownRefresh = () => {
refetch().finally(() => {
Taro.stopPullDownRefresh()
})
}
return (
<ScrollView
className="h-screen"
scrollY
onScrollToLower={handleScrollToLower}
refresherEnabled
refresherTriggered={false}
onRefresherRefresh={onPullDownRefresh}
>
<View className="px-4 py-4">
{isLoading ? (
<View className="flex justify-center py-10">
<View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
</View>
) : (
<>
{allGoods.map((item) => (
<View key={item.id} className="bg-white rounded-lg p-4 mb-3">
<Text>{item.name}</Text>
</View>
))}
{isFetchingNextPage && (
<View className="flex justify-center py-4">
<View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
<Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
</View>
)}
{!hasNextPage && allGoods.length > 0 && (
<View className="text-center py-4 text-sm text-gray-400">
没有更多了
</View>
)}
</>
)}
</View>
</ScrollView>
)
}
// 在schemas目录下定义
import { z } from 'zod'
export const userSchema = z.object({
username: z.string()
.min(3, '用户名至少3个字符')
.max(20, '用户名最多20个字符')
.regex(/^\S+$/, '用户名不能包含空格'),
email: z.string().email('请输入有效的邮箱地址'),
phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号')
})
export type UserFormData = z.infer<typeof userSchema>
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { userSchema, type UserFormData } from '@/schemas/user.schema'
const form = useForm<UserFormData>({
resolver: zodResolver(userSchema),
defaultValues: {
username: '',
email: '',
phone: ''
}
})
// 表单提交
const onSubmit = async (data: UserFormData) => {
try {
await mutation.mutateAsync(data)
} catch (error) {
console.error('表单提交失败:', error)
}
}
const handleApiError = (error: any) => {
const message = error.response?.data?.message || error.message || '操作失败'
if (error.response?.status === 401) {
Taro.showModal({
title: '未登录',
content: '请先登录',
success: () => {
Taro.navigateTo({ url: '/pages/login/index' })
}
})
} else if (error.response?.status === 403) {
Taro.showToast({ title: '权限不足', icon: 'none' })
} else if (error.response?.status === 404) {
Taro.showToast({ title: '资源不存在', icon: 'none' })
} else if (error.response?.status >= 500) {
Taro.showToast({ title: '服务器错误,请稍后重试', icon: 'none' })
} else {
Taro.showToast({ title: message, icon: 'none' })
}
}
const { data, isLoading, error } = useQuery({
// ...查询配置
})
if (error) {
return (
<View className="flex-1 flex items-center justify-center">
<View className="text-center">
<View className="i-heroicons-exclamation-triangle-20-solid w-12 h-12 text-red-500 mx-auto mb-4" />
<Text className="text-gray-600 mb-4">{error.message}</Text>
<Button onClick={() => refetch()}>重新加载</Button>
</View>
</View>
)
}
// 示例:首页
import React from 'react'
import { View, Text } from '@tarojs/components'
import { TabBarLayout } from '@/layouts/tab-bar-layout'
import { Navbar } from '@/components/ui/navbar'
import { Button } from '@/components/ui/button'
import './index.css'
const HomePage: React.FC = () => {
return (
<TabBarLayout activeKey="home">
<Navbar
title="首页"
rightIcon="i-heroicons-bell-20-solid"
onClickRight={() => console.log('点击通知')}
leftIcon=""
/>
<View className="px-4 py-4">
<Text className="text-2xl font-bold text-gray-900">欢迎使用</Text>
<View className="mt-4">
<Text className="text-gray-600">这是一个简洁优雅的小程序首页</Text>
</View>
</View>
</TabBarLayout>
)
}
export default HomePage
// 示例:登录页
import { View } from '@tarojs/components'
import { useEffect } from 'react'
import Taro from '@tarojs/taro'
import { Navbar } from '@/components/ui/navbar'
import { Button } from '@/components/ui/button'
import './index.css'
export default function Login() {
useEffect(() => {
Taro.setNavigationBarTitle({
title: '用户登录'
})
}, [])
return (
<View className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
<Navbar
title="用户登录"
backgroundColor="bg-transparent"
textColor="text-gray-900"
border={false}
/>
<View className="flex-1 px-6 py-12">
{/* Logo区域 */}
<View className="flex flex-col items-center mb-10">
<View className="w-20 h-20 mb-4 rounded-full bg-white shadow-lg flex items-center justify-center">
<View className="i-heroicons-user-circle-20-solid w-12 h-12 text-blue-500" />
</View>
<Text className="text-2xl font-bold text-gray-900 mb-1">欢迎回来</Text>
</View>
{/* 表单区域 */}
<View className="bg-white rounded-2xl shadow-sm p-6">
<View className="space-y-5">
{/* 表单内容 */}
</View>
</View>
</View>
</View>
)
}
// mini/src/app.config.ts
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/explore/index',
'pages/profile/index',
// 其他页面
],
tabBar: {
color: '#666666',
selectedColor: '#1976D2',
backgroundColor: '#ffffff',
borderStyle: 'black',
list: [
{
pagePath: 'pages/index/index',
text: '首页',
iconPath: 'assets/icons/home.png',
selectedIconPath: 'assets/icons/home-active.png'
},
{
pagePath: 'pages/explore/index',
text: '发现',
iconPath: 'assets/icons/explore.png',
selectedIconPath: 'assets/icons/explore-active.png'
},
{
pagePath: 'pages/profile/index',
text: '我的',
iconPath: 'assets/icons/profile.png',
selectedIconPath: 'assets/icons/profile-active.png'
}
]
}
})
非TabBar页面会自动添加到pages数组中,无需额外配置tabBar。
user-profileUserProfilePageuser-profile.tsxbg-gray-50 或 bg-whiteuseAuth hook// Tab页面跳转
Taro.switchTab({ url: '/pages/index/index' })
// 普通页面跳转
Taro.navigateTo({ url: '/pages/login/index' })
// 返回上一页
Taro.navigateBack()
// 重定向(清除当前页面历史)
Taro.redirectTo({ url: '/pages/login/index' })
// 重新启动应用
Taro.reLaunch({ url: '/pages/index/index' })
// 显示提示
Taro.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
// 显示加载
Taro.showLoading({
title: '加载中...',
mask: true
})
Taro.hideLoading()
// 显示确认对话框
Taro.showModal({
title: '确认操作',
content: '确定要执行此操作吗?',
success: (res) => {
if (res.confirm) {
// 用户点击确认
}
}
})
// 显示操作菜单
Taro.showActionSheet({
itemList: ['选项1', '选项2', '选项3'],
success: (res) => {
console.log('用户选择了', res.tapIndex)
}
})
// 存储数据
Taro.setStorageSync('key', 'value')
Taro.setStorageSync('user', JSON.stringify(user))
// 获取数据
const value = Taro.getStorageSync('key')
const user = JSON.parse(Taro.getStorageSync('user') || '{}')
// 移除数据
Taro.removeStorageSync('key')
// 清空所有数据
Taro.clearStorageSync()
```typescript // 获取系统信息 const systemInfo = Taro.getSystemInfoSync() const { screenWidth, screenHeight, windowWidth, windowHeight, statusBarHeight } = systemInfo
// 获取用户位置 Taro.getLocation({ type: 'wgs84', success: (res) => {
console.log('纬度:', res.latitude)
console.log('经度:', res.longitude)
} })