feat(add): 新增多页面新增和编辑表单功能

- 添加编辑和新增收货地址页面,支持表单数据加载和提交
- 新增应用密钥凭证、新增应用操作动态、新增应用成员、新增应用版本页面配置
- 实现文章新增及编辑页面,包含图片上传及多种文章属性配置
- 增加注册会员页面,支持头像上传、手机号获取和邀请人关系处理
- 引入统一表单提交成功和失败处理,支持编辑模式数据回显
- 配置统一eslint和editorconfig规则,增强代码规范和编辑体验
- 新增.gitignore规则,屏蔽无关文件和目录,优化版本管理
This commit is contained in:
2026-04-11 12:22:29 +08:00
commit 07f5c92f4b
627 changed files with 85725 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '骑手中心'
})

197
src/rider/index.tsx Normal file
View File

@@ -0,0 +1,197 @@
import React, { useCallback, useState, useEffect } from 'react'
import Taro from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { Avatar, Button, ConfigProvider, Grid, Empty } from '@nutui/nutui-react-taro'
import { User, Location, Order, Refresh } from '@nutui/icons-react-taro'
import { useThemeStyles } from '@/hooks/useTheme'
import { useUser } from '@/hooks/useUser'
import { listShopOrder } from '@/api/shop/shopOrder'
import type { ShopOrder } from '@/api/shop/shopOrder/model'
const RiderIndex: React.FC = () => {
const themeStyles = useThemeStyles()
const { isLoggedIn, loading: userLoading, getAvatarUrl, getDisplayName } = useUser()
const [stats, setStats] = useState({ pending: 0, delivering: 0, completed: 0, today: 0 })
const [loading, setLoading] = useState(false)
const loadStats = useCallback(async () => {
const userId = Taro.getStorageSync('UserId')
if (!userId) return
setLoading(true)
try {
// 查询待配送和配送中的订单
const [pendingRes, deliveringRes, completedRes] = await Promise.allSettled([
listShopOrder({ riderId: Number(userId), orderStatus: 30, page: 1, limit: 1 }),
listShopOrder({ riderId: Number(userId), orderStatus: 40, page: 1, limit: 1 }),
listShopOrder({ riderId: Number(userId), orderStatus: 50, page: 1, limit: 1 }),
])
const pending = pendingRes.status === 'fulfilled' ? (pendingRes.value?.count || 0) : 0
const delivering = deliveringRes.status === 'fulfilled' ? (deliveringRes.value?.count || 0) : 0
const completed = completedRes.status === 'fulfilled' ? (completedRes.value?.count || 0) : 0
setStats({ pending, delivering, completed, today: pending + delivering })
} catch (e) {
console.error('加载骑手统计失败', e)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
if (isLoggedIn) {
loadStats()
}
}, [isLoggedIn, loadStats])
const navigateToPage = (url: string) => {
if (!isLoggedIn) {
Taro.showToast({ title: '请先登录', icon: 'none', duration: 1500 })
return
}
Taro.navigateTo({ url })
}
if (!isLoggedIn && !userLoading) {
return (
<View className="bg-gray-100 min-h-screen p-4">
<View className="bg-white rounded-xl p-4">
<Text className="text-gray-700"></Text>
<View className="mt-3">
<Button type="primary" onClick={() => Taro.navigateTo({ url: '/passport/login' })}>
</Button>
</View>
</View>
</View>
)
}
const statCards = [
{ label: '待取货', value: stats.pending, color: 'bg-orange-50', textColor: 'text-orange-600' },
{ label: '配送中', value: stats.delivering, color: 'bg-blue-50', textColor: 'text-blue-600' },
{ label: '已完成', value: stats.completed, color: 'bg-green-50', textColor: 'text-green-600' },
{ label: '今日任务', value: stats.today, color: 'bg-purple-50', textColor: 'text-purple-600' },
]
return (
<View className="bg-gray-100 min-h-screen">
{/* 头部信息 */}
<View className="px-4 py-6 relative overflow-hidden" style={themeStyles.primaryBackground}>
<View
className="absolute w-32 h-32 rounded-full"
style={{ backgroundColor: 'rgba(255, 255, 255, 0.1)', top: '-16px', right: '-16px' }}
></View>
<View
className="absolute w-24 h-24 rounded-full"
style={{ backgroundColor: 'rgba(255, 255, 255, 0.08)', bottom: '-12px', left: '-12px' }}
></View>
<View className="flex items-center justify-between relative z-10">
<View className="flex items-center">
<Avatar
size="50"
src={getAvatarUrl()}
icon={<User />}
className="mr-4"
style={{ border: '2px solid rgba(255, 255, 255, 0.3)' }}
/>
<View>
<View className="text-white text-lg font-bold mb-1">{getDisplayName()}</View>
<View className="text-sm" style={{ color: 'rgba(255, 255, 255, 0.8)' }}>
</View>
</View>
</View>
<Button
size="small"
style={{
background: 'rgba(255, 255, 255, 0.18)',
color: '#fff',
border: '1px solid rgba(255, 255, 255, 0.25)'
}}
loading={loading}
onClick={loadStats}
>
<Refresh size="14" />
</Button>
</View>
</View>
{/* 数据统计卡片 */}
<View className="mx-4 -mt-6 rounded-xl bg-white p-4 relative z-10">
<View className="font-semibold text-gray-400 text-sm mb-3"></View>
<View className="grid grid-cols-4 gap-2">
{statCards.map((card) => (
<View key={card.label} className={`${card.color} rounded-lg p-3 text-center`}>
<View className={`text-2xl font-bold ${card.textColor}`}>{card.value}</View>
<View className="text-xs text-gray-500 mt-1">{card.label}</View>
</View>
))}
</View>
</View>
{/* 快捷功能入口 */}
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
<View className="font-semibold mb-4 text-gray-800"></View>
<ConfigProvider>
<Grid
columns={3}
style={{
'--nutui-grid-border-color': 'transparent',
'--nutui-grid-item-border-width': '0px',
border: 'none'
} as React.CSSProperties}
>
<Grid.Item text="待取货" onClick={() => navigateToPage('/rider/orders/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Order color="#f97316" size="20" />
</View>
{stats.pending > 0 && (
<View className="absolute top-0 right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{stats.pending > 9 ? '9+' : stats.pending}
</View>
)}
</View>
</Grid.Item>
<Grid.Item text="配送中" onClick={() => navigateToPage('/rider/orders/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-blue-500 rounded-xl flex items-center justify-center mx-auto mb-2">
<Location color="#3b82f6" size="20" />
</View>
{stats.delivering > 0 && (
<View className="absolute top-0 right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{stats.delivering > 9 ? '9+' : stats.delivering}
</View>
)}
</View>
</Grid.Item>
<Grid.Item text="全部订单" onClick={() => navigateToPage('/rider/orders/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Order color="#6b7280" size="20" />
</View>
</View>
</Grid.Item>
</Grid>
</ConfigProvider>
</View>
{/* 提示信息 */}
{stats.today === 0 && !loading && (
<View className="mx-4 mt-4">
<Empty description="暂无配送任务" imageSize={60} />
</View>
)}
<View className="h-20"></View>
</View>
)
}
export default RiderIndex

View File

@@ -0,0 +1,5 @@
export default definePageConfig({
navigationBarTitleText: '骑手订单',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,50 @@
import {useMemo} from 'react'
import Taro from '@tarojs/taro'
import {Button} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components'
import OrderList from '@/user/order/components/OrderList'
export default function RiderOrders() {
const isLoggedIn = useMemo(() => {
return !!Taro.getStorageSync('access_token') && !!Taro.getStorageSync('UserId')
}, [])
const riderId = useMemo(() => {
const raw = Number(Taro.getStorageSync('UserId'))
return Number.isFinite(raw) && raw > 0 ? raw : undefined
}, [])
if (!isLoggedIn) {
return (
<View className="bg-gray-50 min-h-screen p-4">
<View className="bg-white rounded-lg p-4">
<Text className="text-sm text-gray-700"></Text>
<View className="mt-3">
<Button type="primary" size="small" onClick={() => Taro.navigateTo({url: '/passport/login'})}>
</Button>
</View>
</View>
</View>
)
}
if (!riderId) {
return (
<View className="bg-gray-50 min-h-screen p-4">
<View className="bg-white rounded-lg p-4">
<Text className="text-sm text-gray-700">UserId</Text>
</View>
</View>
)
}
return (
<View className="bg-gray-50 min-h-screen">
<View className="px-3">
<OrderList mode="rider" baseParams={{riderId}} />
</View>
</View>
)
}