爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

907 lines
21 KiB

<template>
<view class="container">
<!-- 顶部绿色区域 -->
<view class="top-section">
<view class="header">
<view class="back-icon" @tap="navigateBack">
<uni-icons type="left" size="20"></uni-icons>
</view>
<text class="title">订单管理</text>
</view>
<!-- 状态标签栏 -->
<view class="status-tabs">
<view
v-for="(tab, index) in statusTabs"
:key="index"
class="tab-item"
:class="{ active: currentStatus === tab.value }"
@tap="switchStatus(tab.value)"
>
<text>{{ tab.label }}</text>
</view>
</view>
</view>
<!-- 筛选栏 -->
<view class="filter-bar">
<view class="filter-item" @tap="showTimePicker">
<text :class="{ 'text-green': showTimePickerModal || selectedTime }">{{ selectedTime || '处理时间' }}</text>
<uni-icons :type="showTimePickerModal ? 'up' : 'down'" size="14" :color="showTimePickerModal || selectedTime ? '#00C853' : '#666'"></uni-icons>
</view>
<view class="search-area">
<view
class="search-box"
:class="{ active: searchActive }"
v-if="searchActive"
>
<uni-icons type="search" size="16" color="#999"></uni-icons>
<input
type="text"
v-model="searchKey"
placeholder="搜索订单号/用户名"
focus
/>
<uni-icons
v-if="searchKey"
type="clear"
size="16"
color="#bbb"
@tap="searchKey = ''"
></uni-icons>
<text class="cancel-btn" @tap="searchActive = false; searchKey = ''">取消</text>
</view>
<view
class="search-icon"
v-else
@tap="searchActive = true"
>
<uni-icons type="search" size="20" color="#999"></uni-icons>
</view>
</view>
</view>
<!-- 时间选择器弹窗 -->
<view
v-if="showTimePickerModal"
class="time-picker-overlay"
@tap="closeTimePicker"
@touchmove.stop.prevent
>
<view
class="time-picker-wrapper"
:style="{
position: 'fixed',
top: timePickerStyle.top + 'px',
left: 0,
width: '100vw'
}"
@tap.stop
>
<view class="time-type-header">
<view
class="type-item"
:class="{ active: activeTimeType === 'start' }"
@tap="switchTimeType('start')"
>
开始时间
<view class="active-line" v-if="activeTimeType === 'start'"></view>
</view>
<view class="type-divider">至</view>
<view
class="type-item"
:class="{ active: activeTimeType === 'end' }"
@tap="switchTimeType('end')"
>
结束时间
<view class="active-line" v-if="activeTimeType === 'end'"></view>
</view>
</view>
<view class="time-picker-content">
<view class="select-mask">
<view class="select-line"></view>
<view class="select-line"></view>
</view>
<picker-view
class="picker-view"
:value="currentDateIndexes"
@change="handlePickerChange"
:indicator-style="'height: 88rpx;'"
>
<picker-view-column>
<view
class="picker-item"
v-for="year in yearOptions"
:key="year"
>
{{year}}年
</view>
</picker-view-column>
<picker-view-column>
<view
class="picker-item"
v-for="month in monthOptions"
:key="month"
>
{{month}}月
</view>
</picker-view-column>
<picker-view-column>
<view
class="picker-item"
v-for="day in dayOptions"
:key="day"
>
{{day}}日
</view>
</picker-view-column>
</picker-view>
</view>
<view class="time-picker-footer">
<view class="btn btn-reset" @tap="resetTimePicker">重置</view>
<view class="btn btn-confirm" @tap="confirmTimePicker">确认</view>
</view>
</view>
</view>
<!-- 订单列表 -->
<scroll-view
scroll-y
class="order-list"
@scrolltolower="loadMore"
>
<view
v-for="(order, index) in filteredOrders"
:key="index"
class="order-item"
>
<view class="order-header">
<text class="order-id">{{ order.orderId }}</text>
<text class="status-tag" :class="order.status">{{ getStatusText(order.status) }}</text>
</view>
<view class="order-info">
<view class="info-item">
<text class="label">用户名称:</text>
<text class="value">{{ order.userName }}</text>
</view>
<view class="info-item">
<text class="label">电话:</text>
<text class="value">{{ order.phone }}</text>
</view>
<view class="info-item">
<text class="label">{{ order.status === 'appointed' ? '预约时间:' : '取件时间:' }}</text>
<text class="value">{{ order.time }}</text>
</view>
</view>
<view class="order-actions">
<view
class="action-btn reject"
v-if="order.status === 'pending'"
@tap="handleReject(order)"
>
<uni-icons type="undo" size="16" color="#666"></uni-icons>
<text>驳回</text>
</view>
<view
class="action-btn approve"
v-if="order.status === 'pending'"
@tap="handleApprove(order)"
>
<uni-icons type="checkmarkempty" size="16" color="#00C853"></uni-icons>
<text>审批</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import pullRefreshMixin from '@/pages/mixins/pullRefreshMixin.js'
export default {
mixins: [pullRefreshMixin],
data() {
return {
// 状态标签配置
statusTabs: [
{ label: '全部', value: 'all' },
{ label: '已预约', value: 'appointed' },
{ label: '待质检', value: 'pending' },
{ label: '待结款', value: 'waiting' },
{ label: '已驳回', value: 'rejected' }
],
currentStatus: 'all',
selectedTime: '',
searchActive: false,
searchKey: '',
showTimePickerModal: false,
timePickerStyle: {
top: 0
},
activeTimeType: 'start',
startDate: {
year: '',
month: '',
day: ''
},
endDate: {
year: '',
month: '',
day: ''
},
orderList: [
{
orderId: 'RE827381278615224',
userName: '周小艺',
phone: '138****1234',
time: '周四 11:00~13:00',
status: 'appointed'
},
{
orderId: 'RE827381278615226',
userName: '周小艺',
phone: '138****1234',
time: '2025-03-20 11:00',
status: 'pending'
},
{
orderId: 'RE827381278615225',
userName: '周小艺',
phone: '138****1234',
time: '2025-03-20 12:00',
status: 'rejected'
},
{
orderId: 'RE827381278615226',
userName: '周小艺',
phone: '138****1234',
time: '2025-03-20 12:00',
status: 'waiting'
}
],
currentDateIndexes: [0, 0, 0], // 当前选中的年月日索引
// 添加时间范围
timeRange: {
start: '',
end: ''
}
}
},
computed: {
// 年份选项
yearOptions() {
const years = []
const currentYear = new Date().getFullYear()
for (let i = 0; i < 6; i++) {
years.push(currentYear + i)
}
return years
},
// 月份选项
monthOptions() {
return Array.from({length: 12}, (_, i) => i + 1)
},
// 日期选项
dayOptions() {
return Array.from({length: 31}, (_, i) => i + 1)
},
// 过滤后的订单列表
filteredOrders() {
let result = this.orderList
// 根据搜索关键词筛选
if (this.searchKey) {
const keyword = this.searchKey.toLowerCase()
result = result.filter(order => {
return order.orderId.toLowerCase().includes(keyword) ||
order.userName.toLowerCase().includes(keyword)
})
}
// 根据状态筛选
if (this.currentStatus !== 'all') {
result = result.filter(order => order.status === this.currentStatus)
}
// 根据时间范围筛选
if (this.timeRange.start && this.timeRange.end) {
const startTime = new Date(this.timeRange.start).getTime()
const endTime = new Date(this.timeRange.end).getTime()
result = result.filter(order => {
// 将订单时间转换为时间戳进行比较
const orderTime = this.parseOrderTime(order.time)
return orderTime >= startTime && orderTime <= endTime
})
}
return result
}
},
methods: {
async onRefresh() {
// 模拟刷新数据
await new Promise(resolve => setTimeout(resolve, 1000))
this.stopPullRefresh()
},
navigateBack() {
uni.navigateBack()
},
// 切换状态
switchStatus(status) {
this.currentStatus = status
},
// 显示时间选择器
showTimePicker() {
const query = uni.createSelectorQuery().in(this);
query.select('.filter-item').boundingClientRect(rect => {
if (rect) {
this.timePickerStyle = {
top: rect.bottom
}
}
this.showTimePickerModal = true;
this.activeTimeType = 'start';
}).exec();
},
// 关闭时间选择器
closeTimePicker() {
this.showTimePickerModal = false
},
// 切换时间类型
switchTimeType(type) {
this.activeTimeType = type
// 更新索引到当前选中的时间
const target = type === 'start' ? this.startDate : this.endDate
this.currentDateIndexes = [
this.yearOptions.indexOf(target.year) || 0,
this.monthOptions.indexOf(target.month) || 0,
this.dayOptions.indexOf(target.day) || 0
]
},
// 处理选择器变化
handlePickerChange(e) {
const values = e.detail.value
const target = this.activeTimeType === 'start' ? this.startDate : this.endDate
// 更新选中的日期
target.year = this.yearOptions[values[0]]
target.month = this.monthOptions[values[1]]
target.day = this.dayOptions[values[2]]
// 更新当前索引
this.currentDateIndexes = values
},
// 重置时间选择
resetTimePicker() {
this.startDate = {
year: '',
month: '',
day: ''
}
this.endDate = {
year: '',
month: '',
day: ''
}
this.currentDateIndexes = [0, 0, 0]
this.activeTimeType = 'start'
// 清空时间范围和显示文本
this.timeRange = {
start: '',
end: ''
}
this.selectedTime = ''
},
// 确认时间选择
confirmTimePicker() {
// 检查开始时间是否完整
if (!this.startDate.year || !this.startDate.month || !this.startDate.day) {
uni.showToast({
title: '请选择完整的开始时间',
icon: 'none'
})
return
}
// 检查结束时间是否完整
if (!this.endDate.year || !this.endDate.month || !this.endDate.day) {
uni.showToast({
title: '请选择完整的结束时间',
icon: 'none'
})
return
}
// 构建时间对象进行比较
const startTime = new Date(this.startDate.year, this.startDate.month - 1, this.startDate.day)
const endTime = new Date(this.endDate.year, this.endDate.month - 1, this.endDate.day)
if (startTime > endTime) {
uni.showToast({
title: '开始时间不能大于结束时间',
icon: 'none'
})
return
}
// 更新显示文本
const start = `${this.startDate.year}${this.startDate.month}${this.startDate.day}`
const end = `${this.endDate.year}${this.endDate.month}${this.endDate.day}`
this.selectedTime = `${start} - ${end}`
// 更新时间范围用于筛选
this.timeRange = {
start: `${this.startDate.year}-${this.startDate.month}-${this.startDate.day}`,
end: `${this.endDate.year}-${this.endDate.month}-${this.endDate.day}`
}
this.showTimePickerModal = false
},
// 搜索处理
handleSearch() {
// 搜索已经通过计算属性自动处理,无需额外操作
},
// 加载更多
loadMore() {
// 实现加载更多逻辑
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
appointed: '已预约',
pending: '待质检',
waiting: '待结款',
rejected: '已驳回'
}
return statusMap[status] || status
},
// 处理驳回
handleReject(order) {
uni.showModal({
title: '提示',
content: '确定要驳回该订单吗?',
success: (res) => {
if (res.confirm) {
// 实现驳回逻辑
}
}
})
},
// 处理审批
handleApprove(order) {
uni.showModal({
title: '提示',
content: '确定要审批通过该订单吗?',
success: (res) => {
if (res.confirm) {
// 实现审批逻辑
}
}
})
},
// 解析订单时间字符串为时间戳
parseOrderTime(timeStr) {
// 处理"周四 11:00~13:00"格式
if (timeStr.includes('周')) {
// 这里可以根据实际需求处理周几的转换
return new Date().getTime() // 临时返回当前时间
}
// 处理"2025-03-20 11:00"格式
return new Date(timeStr).getTime()
}
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background: #f5f5f5;
}
.top-section {
background: #fff;
padding: 60rpx 30rpx 20rpx;
// border-radius: 0 0 30rpx 30rpx;
box-shadow: 0 8rpx 16rpx rgba(0, 200, 83, 0.15);
.header {
// margin-bottom: 30rpx;
display: flex;
align-items: center;
.title {
font-family: PingFang SC;
font-weight: 500;
font-size: 16px;
line-height: 140%;
letter-spacing: 0%;
text-align: center;
vertical-align: middle;
margin-left: 30%;
font-weight: bold;
}
}
.status-tabs {
display: flex;
background: rgba(255,255,255,0.1);
border-radius: 12rpx;
padding: 4rpx;
.tab-item {
flex: 1;
text-align: center;
padding: 16rpx 0;
color: #353535;
font-size: 28rpx;
&.active {
background: #fff;
border-radius: 8rpx;
color: #00C853;
}
}
}
}
.filter-bar {
display: flex;
align-items: center;
background: #fff;
margin: 20rpx;
border-radius: 12rpx;
overflow: visible;
padding: 0 0;
position: relative;
height: 64rpx;
}
.filter-item {
display: flex;
align-items: center;
padding: 0 20rpx;
font-size: 28rpx;
color: #353535;
border-right: 1px solid #eee;
.text-green {
color: #00C853;
}
}
.search-area {
flex: 1;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
position: static;
overflow: visible;
}
.search-box {
display: flex;
align-items: center;
width: 90%;
position: absolute;
left: 0;
right: 0;
top: 0;
height: 100%;
background: #f5f5f5;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
padding: 0 24rpx;
z-index: 10;
transform: translateX(100%);
opacity: 0;
transition: all 0.3s cubic-bezier(.4,0,.2,1);
&.active {
transform: translateX(0);
opacity: 1;
}
input {
flex: 1;
font-size: 28rpx;
margin: 0 10rpx;
background: transparent;
border: none;
outline: none;
}
.cancel-btn {
color: #999;
font-size: 28rpx;
margin-left: 16rpx;
}
}
.search-icon {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 32rpx;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
}
.order-list {
height: calc(100vh - 300rpx);
width: 80%;
margin: 0 auto;
padding: 0 20rpx;
}
.order-item {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.order-id {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.status-tag {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.appointed {
color: #00C853;
background: rgba(0, 200, 83, 0.1);
}
&.pending {
color: #FF9800;
background: rgba(255, 152, 0, 0.1);
}
&.waiting {
color: #2196F3;
background: rgba(33, 150, 243, 0.1);
}
&.rejected {
color: #F44336;
background: rgba(244, 67, 54, 0.1);
}
}
}
.order-info {
.info-item {
display: flex;
margin-bottom: 10rpx;
.label {
color: #999;
font-size: 28rpx;
width: 160rpx;
}
.value {
color: #333;
font-size: 28rpx;
flex: 1;
}
}
}
.order-actions {
display: flex;
justify-content: flex-end;
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1px solid #f5f5f5;
.action-btn {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
margin-left: 20rpx;
border-radius: 8rpx;
text {
font-size: 28rpx;
margin-left: 8rpx;
}
&.reject {
background: #f5f5f5;
color: #666;
}
&.approve {
background: rgba(0, 200, 83, 0.1);
color: #00C853;
}
}
}
}
.time-picker-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: rgba(0,0,0,0.08);
}
.time-picker-wrapper {
background: #fff;
border-radius: 16rpx;
box-shadow: 0 8rpx 32rpx 0 rgba(0,0,0,0.10);
z-index: 101;
}
.time-type-header {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 0;
.type-item {
position: relative;
padding: 0 40rpx;
font-size: 32rpx;
color: #999;
&.active {
color: #00C853;
font-weight: bold;
}
.active-line {
position: absolute;
left: 50%;
bottom: -10rpx;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: #00C853;
border-radius: 2rpx;
}
}
.type-divider {
color: #999;
font-size: 28rpx;
margin: 0 20rpx;
}
}
.time-picker-content {
position: relative;
height: 440rpx;
.select-mask {
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translateY(-44rpx);
height: 88rpx;
z-index: 2;
pointer-events: none;
.select-line {
position: absolute;
left: 0;
right: 0;
height: 1px;
background: rgba(0, 0, 0, 0.1);
&:first-child {
top: 0;
}
&:last-child {
bottom: 0;
}
}
}
.picker-view {
width: 100%;
height: 100%;
.picker-item {
line-height: 88rpx;
text-align: center;
color: #999;
font-size: 32rpx;
}
}
}
/* 选中项样式 */
::v-deep .uni-picker-view-indicator {
height: 88rpx !important;
}
::v-deep .uni-picker-view-mask {
background-image: linear-gradient(180deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(255, 255, 255, 0.6) 45%,
rgba(255, 255, 255, 0) 50%,
rgba(255, 255, 255, 0) 50%,
rgba(255, 255, 255, 0.6) 55%,
rgba(255, 255, 255, 0.95) 100%
);
}
::v-deep .uni-picker-view-indicator::before,
::v-deep .uni-picker-view-indicator::after {
height: 1px;
background-color: rgba(0, 0, 0, 0.1);
}
::v-deep .uni-picker-view-content > view {
color: #999;
font-size: 32rpx;
}
::v-deep .uni-picker-view-indicator view {
color: #000 !important;
font-weight: bold !important;
}
.time-picker-footer {
padding: 20rpx;
display: flex;
justify-content: space-between;
border-top: 1rpx solid #f5f5f5;
.btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 44rpx;
font-size: 32rpx;
margin: 0 10rpx;
&.btn-reset {
background: #f8f8f8;
color: #666;
}
&.btn-confirm {
background: #00C853;
color: #fff;
}
}
}
</style>