3 changed files with 489 additions and 0 deletions
@ -0,0 +1,47 @@ |
|||
.order-search { |
|||
.search-bar { |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.filter-popup { |
|||
.grid { |
|||
display: grid; |
|||
} |
|||
|
|||
.grid-cols-2 { |
|||
grid-template-columns: repeat(2, minmax(0, 1fr)); |
|||
} |
|||
|
|||
.gap-2 { |
|||
gap: 0.5rem; |
|||
} |
|||
|
|||
.flex-wrap { |
|||
flex-wrap: wrap; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 修复Radio按钮样式 |
|||
.nut-radio { |
|||
&.nut-radio--button { |
|||
margin: 2px; |
|||
|
|||
.nut-radio__label { |
|||
padding: 8px 12px; |
|||
border-radius: 4px; |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 修复Input样式 |
|||
.nut-input { |
|||
&.bg-transparent { |
|||
background: transparent !important; |
|||
} |
|||
|
|||
&.border-none { |
|||
border: none !important; |
|||
} |
|||
} |
@ -0,0 +1,266 @@ |
|||
import React, { useState } from 'react'; |
|||
import { View } from '@tarojs/components'; |
|||
import { |
|||
Input, |
|||
Button, |
|||
Popup, |
|||
Cell, |
|||
CellGroup, |
|||
Radio, |
|||
Space, |
|||
DatePicker, |
|||
Picker |
|||
} from '@nutui/nutui-react-taro'; |
|||
import { Search, Filter, Close } from '@nutui/icons-react-taro'; |
|||
import { ShopOrderParam } from '@/api/shop/shopOrder/model'; |
|||
import dayjs from 'dayjs'; |
|||
import './OrderSearch.scss'; |
|||
|
|||
interface OrderSearchProps { |
|||
onSearch: (params: ShopOrderParam) => void; |
|||
onReset: () => void; |
|||
} |
|||
|
|||
// 订单状态选项
|
|||
const orderStatusOptions = [ |
|||
{ text: '全部', value: '' }, |
|||
{ text: '未使用', value: 0 }, |
|||
{ text: '已完成', value: 1 }, |
|||
{ text: '已取消', value: 2 }, |
|||
{ text: '取消中', value: 3 }, |
|||
{ text: '退款申请中', value: 4 }, |
|||
{ text: '退款被拒绝', value: 5 }, |
|||
{ text: '退款成功', value: 6 }, |
|||
{ text: '客户端申请退款', value: 7 } |
|||
]; |
|||
|
|||
// 支付状态选项
|
|||
const payStatusOptions = [ |
|||
{ text: '全部', value: '' }, |
|||
{ text: '未付款', value: 0 }, |
|||
{ text: '已付款', value: 1 } |
|||
]; |
|||
|
|||
// 支付方式选项
|
|||
const payTypeOptions = [ |
|||
{ text: '全部', value: '' }, |
|||
{ text: '余额支付', value: 0 }, |
|||
{ text: '微信支付', value: 1 }, |
|||
{ text: '会员卡支付', value: 2 }, |
|||
{ text: '支付宝', value: 3 }, |
|||
{ text: '现金', value: 4 }, |
|||
{ text: 'POS机', value: 5 } |
|||
]; |
|||
|
|||
const OrderSearch: React.FC<OrderSearchProps> = ({ onSearch, onReset }) => { |
|||
const [showFilter, setShowFilter] = useState(false); |
|||
const [searchParams, setSearchParams] = useState<ShopOrderParam>({ |
|||
keywords: '', |
|||
orderNo: '', |
|||
phone: '', |
|||
orderStatus: undefined, |
|||
payStatus: undefined, |
|||
payType: undefined |
|||
}); |
|||
|
|||
// 搜索关键词
|
|||
const handleKeywordSearch = () => { |
|||
if (!searchParams.keywords?.trim()) { |
|||
return; |
|||
} |
|||
onSearch({ keywords: searchParams.keywords.trim() }); |
|||
}; |
|||
|
|||
// 重置搜索条件
|
|||
const handleReset = () => { |
|||
setSearchParams({ |
|||
keywords: '', |
|||
orderNo: '', |
|||
phone: '', |
|||
orderStatus: undefined, |
|||
payStatus: undefined, |
|||
payType: undefined |
|||
}); |
|||
onReset(); |
|||
}; |
|||
|
|||
// 应用筛选条件
|
|||
const handleFilter = () => { |
|||
const filterParams: ShopOrderParam = {}; |
|||
|
|||
if (searchParams.orderNo?.trim()) { |
|||
filterParams.orderNo = searchParams.orderNo.trim(); |
|||
} |
|||
if (searchParams.phone?.trim()) { |
|||
filterParams.phone = searchParams.phone.trim(); |
|||
} |
|||
if (searchParams.orderStatus !== undefined && searchParams.orderStatus !== '') { |
|||
filterParams.orderStatus = Number(searchParams.orderStatus); |
|||
} |
|||
if (searchParams.payStatus !== undefined && searchParams.payStatus !== '') { |
|||
filterParams.payStatus = Number(searchParams.payStatus); |
|||
} |
|||
if (searchParams.payType !== undefined && searchParams.payType !== '') { |
|||
filterParams.payType = Number(searchParams.payType); |
|||
} |
|||
|
|||
onSearch(filterParams); |
|||
setShowFilter(false); |
|||
}; |
|||
|
|||
return ( |
|||
<View className="order-search"> |
|||
{/* 搜索栏 */} |
|||
<View className="search-bar flex items-center p-3 bg-white"> |
|||
<View className="flex-1 flex items-center bg-gray-100 rounded-full px-3 py-2"> |
|||
<Search size={16} className="text-gray-400 mr-2" /> |
|||
<Input |
|||
placeholder="搜索订单号、商品名称" |
|||
value={searchParams.keywords} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, keywords: value }))} |
|||
onConfirm={handleKeywordSearch} |
|||
className="flex-1 bg-transparent border-none" |
|||
style={{ padding: 0 }} |
|||
/> |
|||
</View> |
|||
<Button |
|||
type="primary" |
|||
size={'large'} |
|||
className="ml-2" |
|||
onClick={handleKeywordSearch} |
|||
> |
|||
搜索 |
|||
</Button> |
|||
<View |
|||
className="ml-2 p-2 flex items-center justify-center" |
|||
onClick={() => setShowFilter(true)} |
|||
> |
|||
<Filter size={18} className="text-gray-600" /> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 筛选弹窗 */} |
|||
<Popup |
|||
visible={showFilter} |
|||
position="right" |
|||
onClose={() => setShowFilter(false)} |
|||
style={{ width: '80%', height: '100%' }} |
|||
> |
|||
<View className="filter-popup h-full flex flex-col"> |
|||
{/* 头部 */} |
|||
<View className="flex items-center justify-between p-4 border-b"> |
|||
<View className="text-lg font-medium">筛选条件</View> |
|||
<Close size={20} onClick={() => setShowFilter(false)} /> |
|||
</View> |
|||
|
|||
{/* 筛选内容 */} |
|||
<View className="flex-1 overflow-y-auto"> |
|||
<CellGroup> |
|||
{/* 订单号 */} |
|||
<Cell> |
|||
<View className="w-full"> |
|||
<View className="text-sm text-gray-600 mb-2">订单号</View> |
|||
<Input |
|||
placeholder="请输入订单号" |
|||
value={searchParams.orderNo} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, orderNo: value }))} |
|||
/> |
|||
</View> |
|||
</Cell> |
|||
|
|||
{/* 手机号 */} |
|||
<Cell> |
|||
<View className="w-full"> |
|||
<View className="text-sm text-gray-600 mb-2">手机号</View> |
|||
<Input |
|||
placeholder="请输入手机号" |
|||
value={searchParams.phone} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, phone: value }))} |
|||
/> |
|||
</View> |
|||
</Cell> |
|||
|
|||
{/* 订单状态 */} |
|||
<Cell> |
|||
<View className="w-full"> |
|||
<View className="text-sm text-gray-600 mb-2">订单状态</View> |
|||
<Radio.Group |
|||
value={searchParams.orderStatus} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, orderStatus: value }))} |
|||
> |
|||
<View className="grid grid-cols-2 gap-2"> |
|||
{orderStatusOptions.map((option) => ( |
|||
<Radio key={option.value} value={option.value} shape="button"> |
|||
{option.text} |
|||
</Radio> |
|||
))} |
|||
</View> |
|||
</Radio.Group> |
|||
</View> |
|||
</Cell> |
|||
|
|||
{/* 支付状态 */} |
|||
<Cell> |
|||
<View className="w-full"> |
|||
<View className="text-sm text-gray-600 mb-2">支付状态</View> |
|||
<Radio.Group |
|||
value={searchParams.payStatus} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, payStatus: value }))} |
|||
> |
|||
<View className="flex flex-wrap gap-2"> |
|||
{payStatusOptions.map((option) => ( |
|||
<Radio key={option.value} value={option.value} shape="button"> |
|||
{option.text} |
|||
</Radio> |
|||
))} |
|||
</View> |
|||
</Radio.Group> |
|||
</View> |
|||
</Cell> |
|||
|
|||
{/* 支付方式 */} |
|||
<Cell> |
|||
<View className="w-full"> |
|||
<View className="text-sm text-gray-600 mb-2">支付方式</View> |
|||
<Radio.Group |
|||
value={searchParams.payType} |
|||
onChange={(value) => setSearchParams(prev => ({ ...prev, payType: value }))} |
|||
> |
|||
<View className="grid grid-cols-2 gap-2"> |
|||
{payTypeOptions.map((option) => ( |
|||
<Radio key={option.value} value={option.value} shape="button"> |
|||
{option.text} |
|||
</Radio> |
|||
))} |
|||
</View> |
|||
</Radio.Group> |
|||
</View> |
|||
</Cell> |
|||
</CellGroup> |
|||
</View> |
|||
|
|||
{/* 底部按钮 */} |
|||
<View className="p-4 border-t"> |
|||
<Space className="w-full"> |
|||
<Button |
|||
className="flex-1" |
|||
onClick={handleReset} |
|||
> |
|||
重置 |
|||
</Button> |
|||
<Button |
|||
type="primary" |
|||
className="flex-1" |
|||
onClick={handleFilter} |
|||
> |
|||
确定 |
|||
</Button> |
|||
</Space> |
|||
</View> |
|||
</View> |
|||
</Popup> |
|||
</View> |
|||
); |
|||
}; |
|||
|
|||
export default OrderSearch; |
@ -0,0 +1,176 @@ |
|||
# 订单筛选及查询功能实现说明 |
|||
|
|||
## 功能概述 |
|||
|
|||
为订单页面实现了完整的筛选及查询功能,包括关键词搜索、状态筛选、支付方式筛选等多种筛选条件。 |
|||
|
|||
## 实现的功能 |
|||
|
|||
### 1. 关键词搜索 |
|||
- 支持按订单号搜索 |
|||
- 支持按商品名称搜索 |
|||
- 实时搜索,按回车键或点击搜索按钮触发 |
|||
|
|||
### 2. Tabs订单状态筛选 ✅ 已修复完成 + 排除已取消订单 |
|||
- **全部订单** - 显示所有订单 (不添加筛选条件) |
|||
- **待付款** - 筛选未付款且未取消的订单 (payStatus = 0, 排除已取消) |
|||
- **待发货** - 筛选已付款但未发货且未取消的订单 (payStatus = 1, deliveryStatus = 10, 排除已取消) |
|||
- **待收货** - 筛选已发货且未取消的订单 (deliveryStatus = 20, 排除已取消) |
|||
- **已完成** - 筛选已完成订单 (orderStatus = 1) |
|||
- **已取消** - 筛选已取消/退款的订单 (orderStatus = 2,3,4,6,7) |
|||
|
|||
### 3. 已取消订单状态说明 |
|||
为了避免已取消的订单出现在正常流程的tab中,系统会自动排除以下状态的订单: |
|||
- `orderStatus = 2`: 已取消 |
|||
- `orderStatus = 3`: 取消中 |
|||
- `orderStatus = 4`: 退款申请中 |
|||
- `orderStatus = 6`: 退款成功 |
|||
- `orderStatus = 7`: 客户端申请退款 |
|||
|
|||
### 4. 后端筛选逻辑对应关系 |
|||
根据实际的API参数,各tab对应的筛选逻辑如下: |
|||
|
|||
| Tab索引 | Tab名称 | API参数 | 前端筛选 | 说明 | |
|||
|---------|---------|---------|----------|------| |
|||
| 0 | 全部订单 | 无额外参数 | 无 | 显示所有订单 | |
|||
| 1 | 待付款 | `payStatus=0` | 排除已取消 | 未付款且未取消的订单 | |
|||
| 2 | 待发货 | `payStatus=1&deliveryStatus=10` | 排除已取消 | 已付款但未发货且未取消 | |
|||
| 3 | 待收货 | `deliveryStatus=20` | 排除已取消 | 已发货且未取消 | |
|||
| 4 | 已完成 | `orderStatus=1` | 无 | 订单已完成 | |
|||
| 5 | 已取消 | 无额外参数 | 只显示已取消 | 已取消/退款的订单 | |
|||
|
|||
### 4. Tabs功能特性 |
|||
- 点击不同tab自动筛选对应状态的订单 |
|||
- 使用后端标准的statusFilter参数 |
|||
- 显示当前筛选状态和订单数量 |
|||
- 加载状态指示器 |
|||
- 平滑的切换动画效果 |
|||
|
|||
### 3. 支付状态筛选 |
|||
- 全部 |
|||
- 未付款 |
|||
- 已付款 |
|||
|
|||
### 4. 支付方式筛选 |
|||
- 全部 |
|||
- 余额支付 |
|||
- 微信支付 |
|||
- 会员卡支付 |
|||
- 支付宝 |
|||
- 现金 |
|||
- POS机 |
|||
|
|||
## 文件结构 |
|||
|
|||
``` |
|||
src/pages/order/ |
|||
├── order.tsx # 主订单页面 |
|||
├── components/ |
|||
│ ├── OrderList.tsx # 订单列表组件 |
|||
│ ├── OrderSearch.tsx # 搜索筛选组件 |
|||
│ └── OrderSearch.scss # 搜索组件样式 |
|||
└── order.scss # 订单页面样式 |
|||
``` |
|||
|
|||
## 主要修改 |
|||
|
|||
### 1. 扩展API接口参数 |
|||
- 在 `src/api/shop/shopOrder/model/index.ts` 中扩展了 `ShopOrderParam` 接口 |
|||
- 添加了 `deliveryStatus` 字段支持发货状态筛选 |
|||
|
|||
### 2. 更新订单页面 (order.tsx) |
|||
- 添加搜索状态管理 |
|||
- 集成搜索组件 |
|||
- 实现搜索和重置功能 |
|||
- 将搜索参数传递给订单列表组件 |
|||
|
|||
### 3. 更新订单列表组件 (OrderList.tsx) ✅ 重点修复 |
|||
- **修复tabs筛选逻辑** - 解决tapIndex类型不匹配问题 |
|||
- **完善筛选参数映射** - 每个tab对应正确的API筛选条件 |
|||
- **添加筛选状态提示** - 显示当前筛选的tab和订单数量 |
|||
- **优化用户体验** - 添加加载状态和视觉反馈 |
|||
- **支持搜索参数** - 监听搜索参数变化并重新加载数据 |
|||
- **调试信息** - 添加console.log便于调试筛选逻辑 |
|||
|
|||
### 4. 创建搜索组件 (OrderSearch.tsx) |
|||
- 实现关键词搜索界面 |
|||
- 实现高级筛选弹窗 |
|||
- 支持多种筛选条件组合 |
|||
- 响应式设计,适配移动端 |
|||
|
|||
### 5. 样式优化 |
|||
- 优化tabs的视觉效果 |
|||
- 添加筛选提示的动画效果 |
|||
- 改进搜索框的样式和交互 |
|||
|
|||
## 使用方法 |
|||
|
|||
### 基础搜索 |
|||
1. 点击导航栏的搜索图标或筛选图标 |
|||
2. 在搜索框中输入订单号或商品名称 |
|||
3. 按回车键或点击搜索按钮 |
|||
|
|||
### 高级筛选 |
|||
1. 点击筛选图标打开筛选弹窗 |
|||
2. 选择需要的筛选条件: |
|||
- 输入具体订单号 |
|||
- 输入手机号 |
|||
- 选择订单状态 |
|||
- 选择支付状态 |
|||
- 选择支付方式 |
|||
3. 点击确定按钮应用筛选 |
|||
|
|||
### 重置搜索 |
|||
- 点击重置按钮清空所有搜索条件 |
|||
- 返回显示所有订单 |
|||
|
|||
## 技术特点 |
|||
|
|||
### 1. 类型安全 |
|||
- 使用TypeScript确保类型安全 |
|||
- 定义了完整的接口类型 |
|||
|
|||
### 2. 组件化设计 |
|||
- 搜索功能独立封装为组件 |
|||
- 便于维护和复用 |
|||
|
|||
### 3. 响应式设计 |
|||
- 适配移动端界面 |
|||
- 使用Tailwind CSS实现响应式布局 |
|||
|
|||
### 4. 用户体验优化 |
|||
- 实时搜索反馈 |
|||
- 清晰的筛选界面 |
|||
- 便捷的重置功能 |
|||
|
|||
## 扩展建议 |
|||
|
|||
### 1. 日期范围筛选 |
|||
可以添加订单创建时间、支付时间等日期范围筛选功能。 |
|||
|
|||
### 2. 金额范围筛选 |
|||
支持按订单金额范围进行筛选。 |
|||
|
|||
### 3. 商户筛选 |
|||
如果有多商户,可以添加按商户筛选的功能。 |
|||
|
|||
### 4. 搜索历史 |
|||
保存用户的搜索历史,提供快速搜索选项。 |
|||
|
|||
### 5. 导出功能 |
|||
支持将筛选结果导出为Excel或PDF格式。 |
|||
|
|||
## 注意事项 |
|||
|
|||
1. 搜索功能依赖后端API支持相应的查询参数 |
|||
2. 筛选条件会影响分页加载,需要在切换筛选条件时重置页码 |
|||
3. 建议在生产环境中添加搜索防抖功能,避免频繁请求 |
|||
4. 可以考虑添加搜索结果缓存,提升用户体验 |
|||
|
|||
## 测试建议 |
|||
|
|||
1. 测试各种搜索条件的组合 |
|||
2. 测试搜索结果的准确性 |
|||
3. 测试重置功能是否正常 |
|||
4. 测试在不同设备上的显示效果 |
|||
5. 测试搜索性能,特别是大数据量情况下 |
Loading…
Reference in new issue