Files
shangbangbang/uniapp/pages/sales/sales.vue
T
solosw 99b11b04e4 1.0
2026-01-05 14:11:34 +08:00

662 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="container">
<!-- 状态切换标签 -->
<view class="status-tabs">
<view class="tab-item"
:class="{ active: currentStatus === 'all' }"
@click="switchStatus('all')">
<text class="tab-text">全部</text>
</view>
<view class="tab-item"
:class="{ active: currentStatus === '0' }"
@click="switchStatus('0')">
<text class="tab-text">待付款</text>
</view>
<view class="tab-item"
:class="{ active: currentStatus === '1' }"
@click="switchStatus('1')">
<text class="tab-text">待确认</text>
</view>
<view class="tab-item"
:class="{ active: currentStatus === '2' }"
@click="switchStatus('2')">
<text class="tab-text">已完成</text>
</view>
</view>
<!-- 筛选条件 -->
<view class="filter-bar">
<view class="filter-item" @click="showDatePicker('start')">
<text class="filter-label">开始日期</text>
<text class="filter-value">{{ startDate || '选择日期' }}</text>
</view>
<view class="filter-item" @click="showDatePicker('end')">
<text class="filter-label">结束日期</text>
<text class="filter-value">{{ endDate || '选择日期' }}</text>
</view>
<view class="filter-item" @click="showAmountFilter">
<text class="filter-label">按日期筛选</text>
<text class="filter-value">总金额</text>
</view>
</view>
<!-- 总金额显示 -->
<view class="amount-summary" v-if="totalAmount > 0">
<text class="summary-label">重置</text>
<text class="summary-amount">{{ totalAmount }}</text>
</view>
<!-- 卖单列表 -->
<view class="sales-list">
<view class="sales-item" v-for="(item, index) in salesList" :key="index" @click="goToOrderDetail(item)">
<view class="order-header">
<text class="order-number">订单编号{{ item.order_no }}</text>
<view class="order-status" :class="getStatusClass(item.order_status)">
<text class="status-text">{{ getStatusText(item.order_status) }}</text>
</view>
</view>
<view class="order-content">
<view class="product-info">
<image :src="getFullImageUrl(getFirstImage(item.product_images))"
class="product-image"
mode="aspectFill">
</image>
<view class="product-details">
<text class="product-name">{{ item.product_name }}</text>
<text class="product-price">¥{{ item.selling_price }}</text>
<text class="product-quantity">×{{ item.quantity }}</text>
</view>
</view>
</view>
<view class="order-footer">
<text class="order-time">下单时间{{ formatTime(item.created_at) }}</text>
<view class="order-summary">
<!-- <text class="summary-text">{{ item.quantity }}件商品 运费 ¥0.00 合计</text> -->
<text class="summary-price">¥{{ item.total_amount }}</text>
</view>
</view>
<view class="order-actions">
<!-- <button class="action-btn detail-btn" @click.stop="goToDetail(item)">
查看详情
</button> -->
<button v-if="item.payment_proof_image"
class="action-btn proof-btn"
@click.stop="viewPaymentProof(item)">
查看凭证
</button>
<button v-if="item.order_status === 1"
class="action-btn confirm-btn"
@click.stop="confirmOrder(item)">
确认发货
</button>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="salesList.length === 0 && !loading">
<uni-icons type="shop" size="100" color="#ccc"></uni-icons>
<text class="empty-text">暂无卖单</text>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore && salesList.length > 0">
<text>加载更多...</text>
</view>
</view>
</template>
<script>
import request from '@/utils/request.js'
export default {
data() {
return {
currentStatus: 'all',
startDate: '',
endDate: '',
totalAmount: 0,
salesList: [],
currentPage: 1,
pageSize: 10,
hasMore: true,
loading: false
}
},
onLoad(options) {
// 检查登录状态
const token = uni.getStorageSync('token')
if (!token) {
uni.reLaunch({
url: '/pages/login/login'
})
return
}
if (options.status) {
this.currentStatus = options.status
}
this.loadSales()
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore()
}
},
methods: {
// 获取完整的图片URL
getFullImageUrl(url) {
return request.getImageUrl(url)
},
// 获取商品的第一张图片
getFirstImage(images) {
if (!images) return ''
if (typeof images === 'string') {
return images.split(',')[0].trim()
}
if (Array.isArray(images) && images.length > 0) {
return images[0]
}
return ''
},
// 切换状态
switchStatus(status) {
this.currentStatus = status
this.currentPage = 1
this.loadSales()
},
// 显示日期选择器
showDatePicker(type) {
uni.showActionSheet({
itemList: ['今天', '最近7天', '最近30天', '最近90天'],
success: (res) => {
const now = new Date()
let startDate, endDate
switch (res.tapIndex) {
case 0: // 今天
startDate = endDate = this.formatDate(now)
break
case 1: // 最近7天
startDate = this.formatDate(new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000))
endDate = this.formatDate(now)
break
case 2: // 最近30天
startDate = this.formatDate(new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000))
endDate = this.formatDate(now)
break
case 3:
startDate = this.formatDate(new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000))
endDate = this.formatDate(now)
break
}
if (type === 'start') {
this.startDate = startDate
} else {
this.endDate = endDate
}
this.currentPage = 1
this.loadSales()
}
})
},
// 显示金额筛选
showAmountFilter() {
this.startDate = ''
this.endDate = ''
this.currentPage = 1
this.loadSales()
},
// 加载卖单列表
async loadSales() {
this.loading = true
try {
const params = {
page: this.currentPage,
page_size: this.pageSize
}
if (this.currentStatus !== 'all') {
// 状态映射:前端显示状态 -> 后端查询参数
const statusMap = {
'0': 'pending',
'1': 'confirming',
'2': 'completed',
'3': 'cancelled'
}
params.status = statusMap[this.currentStatus] || this.currentStatus
}
if (this.startDate) {
params.start_date = this.startDate
}
if (this.endDate) {
params.end_date = this.endDate
}
const res = await request.get('/back/user/orders/sales', params)
if (res.success) {
const data = res.data
if (this.currentPage === 1) {
this.salesList = data.list || []
} else {
this.salesList = [...this.salesList, ...(data.list || [])]
}
this.hasMore = this.salesList.length < data.total
this.totalAmount = data.total_amount || 0
}
} catch (error) {
console.error('加载卖单失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 加载更多
loadMore() {
this.currentPage++
this.loadSales()
},
// 获取状态样式类
getStatusClass(status) {
const classMap = {
0: 'status-pending',
1: 'status-confirming',
2: 'status-completed',
3: 'status-cancelled'
}
return classMap[status] || ''
},
// 获取状态文本
getStatusText(status) {
const textMap = {
0: '待付款',
1: '待确认',
2: '已完成',
3: '已取消'
}
return textMap[status] || '未知状态'
},
// 格式化时间
formatTime(time) {
if (!time) return ''
const date = new Date(time)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
},
// 格式化日期
formatDate(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
},
// 跳转到订单详情
goToDetail(order) {
uni.navigateTo({
url: `/pages/order/detail?id=${order.id}&type=sales`
})
},
// 跳转到订单详情页面
goToOrderDetail(order) {
uni.navigateTo({
url: `/pages/orders/detail?id=${order.id}&type=sales`
})
},
// 查看付款凭证
viewPaymentProof(order) {
if (!order.payment_proof_image) {
uni.showToast({
title: '暂无付款凭证',
icon: 'none'
})
return
}
uni.previewImage({
urls: [this.getFullImageUrl(order.payment_proof_image)],
current: 0
})
},
// 确认发货
async confirmOrder(order) {
uni.showModal({
title: '确认发货',
content: `确认向买家发货 ¥${order.total_amount} 的商品吗?确认后买家将收到商品。`,
success: async (res) => {
if (res.confirm) {
try {
const result = await request.put(`/back/user/orders/sales/${order.id}/confirm`)
if (result.success) {
uni.showToast({
title: '确认发货成功',
icon: 'success'
})
this.loadSales()
} else {
throw new Error(result.message || '确认失败')
}
} catch (error) {
console.error('确认发货失败:', error)
uni.showToast({
title: error.message || '确认失败',
icon: 'none'
})
}
}
}
})
},
// 取消订单
async cancelOrder(order) {
uni.showModal({
title: '确认取消',
content: '确定要取消这个订单吗?',
success: async (res) => {
if (res.confirm) {
try {
const result = await request.post(`/back/user/orders/sales/${order.id}/cancel`)
if (result.success) {
uni.showToast({
title: '订单已取消',
icon: 'success'
})
this.loadSales()
}
} catch (error) {
uni.showToast({
title: '取消失败',
icon: 'none'
})
}
}
}
})
}
}
}
</script>
<style scoped>
.container {
background-color: #f5f5f5;
min-height: 100vh;
}
/* 样式与买单页面基本一致,只调整部分颜色和文本 */
.status-tabs {
background-color: #fff;
display: flex;
padding: 0 30rpx;
}
.tab-item {
flex: 1;
text-align: center;
padding: 30rpx 0;
position: relative;
}
.tab-text {
font-size: 28rpx;
color: #666;
}
.tab-item.active .tab-text {
color: #FF4444;
font-weight: bold;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #FF4444;
}
.filter-bar {
background-color: #fff;
display: flex;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #eee;
}
.filter-item {
flex: 1;
text-align: center;
}
.filter-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 10rpx;
}
.filter-value {
font-size: 28rpx;
color: #333;
}
.amount-summary {
background-color: #fff;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.summary-label {
font-size: 28rpx;
color: #007AFF;
}
.summary-amount {
font-size: 32rpx;
font-weight: bold;
color: #FF4444;
}
.sales-list {
padding: 20rpx;
}
.sales-item {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.order-number {
font-size: 24rpx;
color: #666;
}
.order-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
.status-pending {
background-color: #FFF2E6;
color: #FF8800;
}
.status-confirming {
background-color: #E6F7FF;
color: #1890FF;
}
.status-completed {
background-color: #F6FFED;
color: #52C41A;
}
.status-cancelled {
background-color: #FFF1F0;
color: #FF4D4F;
}
.order-content {
padding: 30rpx;
}
.product-info {
display: flex;
}
.product-image {
width: 160rpx;
height: 120rpx;
border-radius: 8rpx;
margin-right: 20rpx;
background-color: #f5f5f5;
}
.product-details {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.product-price {
font-size: 32rpx;
color: #FF4444;
font-weight: bold;
}
.product-quantity {
font-size: 24rpx;
color: #999;
}
.order-footer {
padding: 30rpx;
border-top: 1rpx solid #f5f5f5;
}
.order-time {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 20rpx;
}
.order-summary {
display: flex;
justify-content: space-between;
align-items: center;
}
.summary-text {
font-size: 24rpx;
color: #666;
}
.summary-price {
font-size: 32rpx;
color: #FF4444;
font-weight: bold;
}
.order-actions {
display: flex;
justify-content: flex-end;
padding: 30rpx;
gap: 20rpx;
border-top: 1rpx solid #f5f5f5;
}
.action-btn {
padding: 16rpx 32rpx;
border-radius: 8rpx;
font-size: 24rpx;
border: 1rpx solid #ddd;
}
.detail-btn {
background-color: #fff;
color: #666;
}
.proof-btn {
background-color: #1890FF;
color: #fff;
border-color: #1890FF;
}
.confirm-btn {
background-color: #52C41A;
color: #fff;
border-color: #52C41A;
}
.cancel-btn {
background-color: #fff;
color: #999;
}
.empty-state {
text-align: center;
padding: 200rpx 0;
}
.empty-state uni-icons {
margin-bottom: 40rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
.load-more {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 24rpx;
}
</style>